然后就一直按照123走,直到做完。
但是如果每次都找一遍最大值,那就是超时的60分O(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种:
开多一个数组,存按疲劳值排序最大的,然后每次都在那里面中找出最右边的,找右边的疲劳值+距离最大的,一组合就是最佳方案。但是这种算法有个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种:就是把环全部都走一遍,然后就找出最小值。