創新班21課總結

創新班早上還是講了salesman那3題,下午就講了關於樹的圖論。
先是salesman,有3種做法:
第1種:

創新班20課3題+總結 - 周正華 - 周正華的博客

 

然後就一直按照123走,直到做完。

但是如果每次都找一遍最大值,那就是超時的60O(n?)算法了,但是我們還要進一步優化,那就是用勝者樹來維護左邊那一部分的最大值(這個我也搞了好久)。每次找到左邊後就把左邊的修改成-1。找到右邊,就把找到的位置與當前位置之間的都插入進勝者樹中。這樣左邊就能找到最大值。至於右邊,就預處理一下,求出當前節點及其右邊的Xi+2Di的最大值的位置,到時求maxp[當前節點+1]的值

#include <iostream> #include <fstream> using namespace std; ifstream fin("salesman.in"); ofstream fout("salesman.out"); #define cin fin #define cout fout int n,d; int s[100001]; int a[100001]; int ans=0; struct Tnode { int num,pos; }; Tnode tree[4*100001]; int maxp[100010]; int test=0; void _insert(int x,int pos) { pos+=d; tree[pos].num=x; tree[pos].pos=pos-d; int now=pos/2; while(now>=1) { if(tree[now*2].num>tree[now*2+1].num) tree[now]=tree[now*2]; else tree[now]=tree[now*2+1]; now/=2; } } void build() { d=1; while(d<n) d*=2; for(int i=1;i<=n;i++) _insert(a[i],i); } int askmax(int l,int r,int i,int j,int pos) { int left,right; if (l==i && r==j) return tree[pos].pos; if (j<=(l+r)/2) return askmax(l,(l+r)/2,i,j,pos*2); else if (i > (l+r) / 2) return askmax((l+r)/2+1,r,i,j,pos*2+1); left = tree[askmax(l,(l+r)/2,i,(l+r)/2,pos*2)].pos; right = tree[askmax((l+r)/2+1,r,(l+r)/2+1,j,pos*2+1)].pos; if(tree[left].num>tree[right].num) return left; else return right; } int main() { cin>>n; for(int i=1;i<=n;i++) cin>>s[i]; for(int i=1;i<=n;i++) cin>>a[i]; build(); maxp[n]=n; for(int i=n-1;i>0;i--) { int x,y; x=s[maxp[i+1]]*2+a[maxp[i+1]]; y=s[i]*2+a[i]; if(x>y) maxp[i]=maxp[i+1]; else maxp[i]=i; } ans+=a[maxp[1]]+s[maxp[1]]*2; cout<<ans<<endl; int x,y,np=maxp[1]; _insert(-1,np); for(int i=2;i<=n;i++) { if(np==n) { ans+=tree[1].num; _insert(-1,tree[1].pos); cout<<ans<<endl; continue; } x=askmax(1,n,1,np-1,1); y=maxp[np+1]; if(tree[x+d].num>=tree[y+d].num+(s[y]-s[np])*2) ans+=tree[1].num,_insert(-1,tree[1].pos); else { ans+=tree[y+d].num+(s[y]-s[np])*2; for(int j=np+1;j<=y;j++) _insert(a[j],j); np=tree[y+d].pos; _insert(-1,np); } cout<<ans<<endl; } return 0; }


第2種:

創新班21課總結 - 周正華 - 周正華的博客

 開多一個數組,存按疲勞值排序最大的,然後每次都在那裏面中找出最右邊的,找右邊的疲勞值+距離最大的,一組合就是最佳方案。但是這種算法有個bug,就是當找到最右邊的位置就在邊邊時,那就找不到它右邊的疲勞值加距離的了。這時候就只要把這個點作爲疲勞值+距離的點,然後以後就一直找純疲勞值的最大值就行了。

#include <iostream> #include <fstream> #include <algorithm> using namespace std; ifstream fin("salesman.in"); ofstream fout("salesman.out"); #define cin fin #define cout fout int n,d; int s[100001]; int a[100001]; struct Tnode { int s,a,pos; }; Tnode num[100001]; int maxp[100010]; bool cmp(Tnode x,Tnode y) { return x.a>y.a; } int main() { cin>>n; for(int i=1;i<=n;i++) cin>>s[i],num[i].s=s[i],num[i].pos=i; for(int i=1;i<=n;i++) cin>>a[i],num[i].a=a[i]; maxp[n]=n; for(int i=n-1;i>0;i--) { int x,y; x=s[maxp[i+1]]*2+a[maxp[i+1]]; y=s[i]*2+a[i]; if(x>y) maxp[i]=maxp[i+1]; else maxp[i]=i; } num[0].a=0; num[0].s=-1; sort(num+1,num+n+1,cmp); int pos=0,snum; int sum=0; int tn=n; bool f=0; for(int k=1;k<=tn;k++) { if(num[k-1].pos==n) { f=1; snum=s[n]*2+a[n]; } if(f) { sum+=num[k].a; cout<<sum+snum<<endl; continue; } sum+=num[k-1].a; if(s[pos]<num[k-1].s) pos=num[k-1].pos; snum=s[maxp[pos+1]]*2+a[maxp[pos+1]]; cout<<sum+snum<<endl; } return 0; }

第3種:就是楊鴻飛講的方法。

也是把疲勞值排序,然後把每次從當前位置左邊的找到的最大值與右邊用預處理找到的最大值比較,看一下那更更大就要哪個。不過編起來應該跟第2種差不多,我就不發了。

然後是stone:

stone用二分答案比較簡單做,就是二分一個最短跳躍距離的最大值,然後順着跳一遍,如果能跳的過去就搜右邊的,不然就搜左邊的。

#include <iostream> #include <fstream> using namespace std; ifstream fin("stone.in"); ofstream fout("stone.out"); //#define cin fin //#define cout fout int L,n,m; int d[50003]; int main() { cin>>L>>n>>m; d[0]=0;d[n+1]=L; for(int i=1;i<=n;i++) cin>>d[i]; int l=1,r=L,mid=0; int step=0; while(l+1<r) { mid=(l+r)/2; for(int i=0;i<=n;i++) if(d[i+1]-d[i]<mid) step++,i++; // cout<<step<<" "<<mid<<" "<<l<<" "<<r<<endl; if(step<=m) l=mid; else r=mid; step=0; } cout<<l; return 0; }

最後是message,它也有3種解法:

第1種:並查集。就是並起來後找環形,就是不斷把所有葉節點給刪掉,只留下那些環,就能求出那其中的最小值。

第2種:時間標記。這個比較好用,就是隨便找一個沒有用過的節點,從它開始走,不斷找它的父親,如果找到這次已經找過了的,那就用這次的時間減上次找到的時間+1,這就是一個環的長度。

#include <iostream> #include <fstream> using namespace std; ifstream fin("message.in"); ofstream fout("message.out"); #define cin fin #define cout fout int n; int fa[2000001]; int d[2000001]; int main() { cin>>n; for(int i=1;i<=n;i++) cin>>fa[i]; int now=1; int sum,ans=100000000; int j=0; int start=0; for(int i=1;i<=n;i++) { if(d[i]!=0) continue; j=i;start=now; do { d[j]=now; now++; j=fa[j]; } while(d[j]==0); if(d[j]>=start) ans=min(ans,now-d[j]); // cout<<now<<" "<<d[j]<<" "<<j<<endl; } cout<<ans<<endl; /* for(int i=1;i<=n;i++) cout<<d[i]<<" "; cout<<endl; */ return 0; }

第3種:就是把環全部都走一遍,然後就找出最小值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章