每個程序員都會編程序,區別在於效率.各位可以試試一下這幾道題,相信大家都能做,但是能用32M以內內存在1秒內解決嗎?這就是傳說中樓教主的男人八題,折騰了一個月,中間也是斷斷續續,今天終於做完了,教主果然名不虛傳啊,再次膜拜教主.有興趣的同學可以到POJ上提交自己的程序.
第一題
題意:n個各不相同的點能組成多少無向連通圖?
解:首先要搞清楚,這題必然要用高精度,因為隨着n的增長無向連通圖的數目的增長將比卡特蘭數列更加猛烈.我用的方法是先統計出總共能組成多少無向圖,再減去其中不聯通的個數.設i個點能組成的無向連通圖個數為a[i].n個點之間共有C(n,2)條邊可連,總圖個數為2^C(n,2).假設圖不連通,那么節點1必定在某個連通分量中,由於圖不連通,所以節點1所在連通分量節點個數可能為i=1~n-1,則剩下的k=n-i個點可以任意連接,所以a[n]=Σ(i=1->n-1){a[i]*2^C(k,2)}.想清楚之后關鍵問題就在於高精度了,借鑒了別人的代碼之后,我又花了一下午的時間自己寫了一個完全高精度模板,又在VIJOS上刷了十幾道高精度題測試,然后再來寫這道題,神奇的1A.

#include<cstdio> #include<cstring> #include<iostream> using namespace std; #ifndef BIGNUM #define BIGNUM class BigNum { #define MAXSIZEOFBIGNUM 500 #define BASE 10 #define DLEN 1 public: int Len; int d[MAXSIZEOFBIGNUM]; public: BigNum(void); BigNum(const int); BigNum(const char *); BigNum(const BigNum &); BigNum & operator = (const BigNum &); void clear(void); friend istream& operator>>(istream&,BigNum&); friend ostream& operator<<(ostream&,BigNum&); bool operator == (const BigNum &) const; bool operator > (const BigNum &) const; bool operator < (const BigNum &) const; bool operator >= (const BigNum &) const; bool operator <= (const BigNum &) const; BigNum operator + (const BigNum &) const; BigNum operator - (const BigNum &) const; BigNum operator * (const BigNum &) const; BigNum operator / (const BigNum &) const; BigNum operator % (const BigNum &) const; void operator ++ (void); void operator -- (void); BigNum operator + (const int &) const; BigNum operator - (const int &) const; BigNum operator * (const int &) const; BigNum operator / (const int &) const; int operator % (const int &) const; BigNum operator ^ (const int &) const; ~BigNum () {} }; BigNum::BigNum(){ Len=0; memset(d,0,sizeof(d)); } BigNum::BigNum(const int ops){ int x=ops; Len=0; memset(d,0,sizeof(d)); while (x) { Len++; d[Len]=x%BASE; x/=BASE; } } BigNum::BigNum(const char * ops){ int L=strlen(ops)-1,b=0; memset(d,0,sizeof(d)); while (ops[b]=='0') b++; Len=0; while (L-b+1>=DLEN) { int x=0; for (int i=L-DLEN+1;i<=L;i++) x=x*10+ops[i]-'0'; Len++; d[Len]=x; L-=DLEN; } int x=0; for (int i=b;i<=L;i++) x=x*10+ops[i]-'0'; Len++; d[Len]=x; } BigNum::BigNum(const BigNum &ops):Len(ops.Len){ memset(d,0,sizeof(d)); for(int i=1;i<=Len;i++) d[i]=ops.d[i]; } BigNum & BigNum::operator = (const BigNum &ops){ memset(d,0,sizeof(d)); Len=ops.Len; for(int i=1;i<=Len;i++) d[i]=ops.d[i]; return *this; } void BigNum::clear(void){ for (int i=1;i<=MAXSIZEOFBIGNUM-2;i++) { if (d[i]<0) { d[i]+=BASE; d[i+1]--; } if (d[i]>=BASE) { d[i]-=BASE; d[i+1]++; } } for (int i=MAXSIZEOFBIGNUM-1;i>=1;i--) if (d[i]>0) { Len=i; return; } Len=0; } istream& operator>>(istream &in,BigNum &ops){ char str[MAXSIZEOFBIGNUM+100]; in>>str; int L=strlen(str),b=0; while (str[b]=='0') b++; ops.Len=0; for (int i=L-1;i>=b;i--) { ops.Len++; ops.d[ops.Len]=str[i]-'0'; } return in; } ostream& operator<<(ostream& out,BigNum& ops){ for (int i=ops.Len;i>=1;i--) out<<ops.d[i]; if (ops.Len==0) out<<"0"; return out; } bool BigNum::operator == (const BigNum &ops) const{ if (Len!=ops.Len) return false; for (int i=Len;i>=1;i--) if (d[i]!=ops.d[i]) return false; return true; } bool BigNum::operator > (const BigNum &ops) const{ if (Len<ops.Len) return false; else if (Len>ops.Len) return true; else { for (int i=Len;i>=1;i--) if (d[i]<ops.d[i]) return false; else if (d[i]>ops.d[i]) return true; } return false; } bool BigNum::operator < (const BigNum &ops) const{ if (Len<ops.Len) return true; else if (Len>ops.Len) return false; else { for (int i=Len;i>=1;i--) if (d[i]<ops.d[i]) return true; else if (d[i]>ops.d[i]) return false; } return false; } bool BigNum::operator >= (const BigNum &ops) const{ if (Len<ops.Len) return false; else if (Len>ops.Len) return true; else { for (int i=Len;i>=1;i--) if (d[i]<ops.d[i]) return false; else if (d[i]>ops.d[i]) return true; } return true; } bool BigNum::operator <= (const BigNum &ops) const{ if (Len<ops.Len) return true; else if (Len>ops.Len) return false; else { for (int i=Len;i>=1;i--) if (d[i]<ops.d[i]) return true; else if (d[i]>ops.d[i]) return false; } return true; } BigNum BigNum::operator + (const BigNum &ops) const{ BigNum ret(*this); for (int i=1;i<=ops.Len;i++) ret.d[i]+=ops.d[i]; ret.clear(); return ret; } BigNum BigNum::operator - (const BigNum &ops) const{ BigNum ret(*this); for (int i=ops.Len;i>=1;i--) ret.d[i]-=ops.d[i]; ret.clear(); return ret; } BigNum BigNum::operator * (const BigNum &ops) const{ BigNum ret,now(*this); for (int i=1;i<=now.Len;i++) for (int j=1;j<=ops.Len;j++) ret.d[i+j-1]+=now.d[i]*ops.d[j]; for (int i=1;i<=MAXSIZEOFBIGNUM-2;i++) if (ret.d[i]>=BASE) { ret.d[i+1]+=ret.d[i]/BASE; ret.d[i]%=BASE; } for (int i=MAXSIZEOFBIGNUM-1;i>=1;i--) if (ret.d[i]>0) { ret.Len=i; break; } return ret; } BigNum BigNum::operator / (const BigNum &ops) const{ BigNum now=(*this),div,mod; div.Len=now.Len; mod.Len=0; for (int j=now.Len;j>=1;j--) { mod.Len++; for (int p=mod.Len;p>=2;p--) mod.d[p]=mod.d[p-1]; mod.d[1]=now.d[j]; while (mod>=ops) { div.d[j]++; mod=mod-ops; } if (mod.Len==1 && mod.d[1]==0) mod.Len--; } div.clear(); mod.clear(); return div; } BigNum BigNum::operator % (const BigNum &ops) const{ BigNum now=(*this),div,mod; div.Len=now.Len; mod.Len=0; for (int j=now.Len;j>=1;j--) { mod.Len++; for (int p=mod.Len;p>=2;p--) mod.d[p]=mod.d[p-1]; mod.d[1]=now.d[j]; while (mod>=ops) { div.d[j]++; mod=mod-ops; } if (mod.Len==1 && mod.d[1]==0) mod.Len--; } div.clear(); mod.clear(); return mod; } void BigNum::operator ++ (void){ d[1]++; for (int i=1;i<=MAXSIZEOFBIGNUM-2;i++) if (d[i]>=BASE) { d[i]-=BASE; d[i+1]++; } else break; if (d[Len+1]>0) Len++; } void BigNum::operator -- (void){ d[1]--; for (int i=1;i<=MAXSIZEOFBIGNUM-2;i++) if (d[i]<0) { d[i]+=BASE; d[i+1]--; } else break; if (d[Len]==0) Len--; } BigNum BigNum::operator + (const int & ops) const{ BigNum ret=(*this); ret.d[1]+=ops; ret.clear(); return ret; } BigNum BigNum::operator - (const int & ops) const{ BigNum ret=(*this); ret.d[1]-=ops; ret.clear(); return ret; } BigNum BigNum::operator * (const int & ops) const{ BigNum ret(*this); for (int i=1;i<=ret.Len;i++) ret.d[i]*=ops; for (int i=1;i<=MAXSIZEOFBIGNUM-2;i++) if (ret.d[i]>=BASE) { ret.d[i+1]+=ret.d[i]/BASE; ret.d[i]%=BASE; } for (int i=MAXSIZEOFBIGNUM-1;i>=1;i--) if (ret.d[i]>0) { ret.Len=i; return ret; } ret.Len=0; return ret; } BigNum BigNum::operator / (const int & ops) const{ BigNum ret; int down=0; for(int i=Len;i>=1;i--) { ret.d[i]=(d[i]+down*BASE)/ops; down=d[i]+down*BASE-ret.d[i]*ops; } ret.Len=Len; while(ret.d[ret.Len]==0 && ret.Len>1) ret.Len--; return ret; } int BigNum::operator % (const int &ops) const{ int mod=0; for(int i=Len;i>=1;i--) mod=((mod*BASE)%ops+d[i])%ops; return mod; } BigNum BigNum::operator ^ (const int &ops) const{ BigNum t,ret(1); if(ops==0)return ret; if(ops==1)return *this; int m=ops,i; while(m>1) { t=*this; for(i=1;(i<<1)<=m;i<<=1) t=t*t; m-=i; ret=ret*t; if(m==1)ret=ret*(*this); } return ret; } #endif BigNum C(int N,int K) { BigNum ret(1); for (int i=0;i<K;i++) ret=ret*(N-i); for (int i=1;i<=K;i++) ret=ret/i; return ret; } BigNum f[60]; int main() { f[1]=1; f[2]=1; f[3]=4; f[4]=38; for (int i=5;i<=50;i++) { int pow=i*(i-1)/2; BigNum T=(BigNum)2^pow; for (int j=1;j<i;j++) { BigNum tmp,com; tmp=(BigNum)2^((i-j)*(i-j-1)/2); tmp=tmp*f[j]; com=C(i-1,j-1); tmp=tmp*com; T=T-tmp; } f[i]=T; } int N; while (scanf("%d",&N)!=EOF,N!=0) cout<<f[N]<<endl; return 0; }
第二題
題意:若干堆石子,每次可以任意合並其中相鄰的兩堆,花費代價為兩堆石子個數之和,求最小代價.
解:這題用動規誰都能5分鍾寫完代碼(except the freshmans like me),關鍵n略大,達到50000.於是就要用到神奇的GarsiaWachs算法,朴素實現O(n^2),加平衡樹優化為O(nlogn).不過這題由於數據原因,朴素即過(原文:You may assume the answer will not exceed 1000000000.)不過在看過fanhqme大牛的代碼后發現,同樣是朴素,我自己寫就不一定能過,這就是人和人的差距啊...

#include<stdio.h> int A[50025],ret,N,T; void combine(int k) { int tmp=A[k]+A[k-1],j; ret+=tmp; for (int i=k;i<T-1;i++) A[i]=A[i+1]; T--; for (j=k-1;j>0 && A[j-1]<tmp;j--) A[j]=A[j-1]; A[j]=tmp; while (j>=2 && A[j]>=A[j-2]) { int d=T-j; combine(j-1); j=T-d; } } int main() { while (scanf("%d",&N)!=EOF && N!=0) { for (int i=0;i<N;i++) scanf("%d",&A[i]); T=1; ret=0; for (int i=1;i<N;i++) { A[T++]=A[i]; while (T>=3 && A[T-3]<=A[T-1]) combine(T-2); } while (T>1) combine(T-1); printf("%d\n",ret); } return 0; }
第三題
題意:n*m的地圖,"#"表示不能進入,問從左下角到右下角的不同哈密頓路徑有多少?
解:用插頭DP+狀態壓縮求解,頓時嚇尿了我這沒見過世面的土鱉.大家都推薦陳丹琪的論文,不過我沒咋看懂(看了兩三天),只是知道了幾個基本概念.最基礎的插頭,意思是說當前節點是否在某一方向上和別的點連接.比如一個點(i,j)和(i-1,j)連接,就可以說(i,j)存在一個上插頭,and so on.輪廓線:由於DP是從上到下,自左而右的進行,將已經計算過的點和未計算過的點分割開來的弦就是輪廓線.經過無數遍從頭再來之后我最終放棄了陳丹琪醫生的治療,最后還是這位神牛(是誰不記得了,只記得他的博客是百度"POJ1739"之后的第一條)的代碼讓我明白了到底是個咋回事.限於表達能力,我就不再多說了.

#include<stdio.h> #include<string.h> const int th[]={1,3,9,27,81,243,729,2187,6561,19683}; bool v[15][15]; int dp[15][15][20025]; int plugleft,plugup; int checkbyte_three(int source,int place) { return (source/th[place])%3; } int main() { int N,M; while (scanf("%d%d",&N,&M)!=EOF) { if (N+M==0) return 0; memset(dp,0,sizeof(dp)); memset(v,false,sizeof(v)); for (int i=0;i<N;i++) { getchar(); for (int j=0;j<M;j++) { char ch=getchar(); if (ch=='#') v[i][j]=true; } } dp[0][0][0]=1; for (int i=0;i<N;i++) for (int j=0;j<=M;j++) { if (i==N-1 && j==M) break; else for (int s=0;s<=th[M+1];s++) if (dp[i][j][s]>0) { if (j==M) { if (s/th[M]==0) dp[i+1][0][s*3]+=dp[i][j][s]; continue; } plugleft=checkbyte_three(s,j); plugup=checkbyte_three(s,j+1); if (v[i][j]) { if (plugleft==0 && plugup==0) dp[i][j+1][s]+=dp[i][j][s]; continue; } if (plugleft==0 && plugup==0) { int trans=s+th[j]+th[j+1]*2; dp[i][j+1][trans]+=dp[i][j][s]; } if ((plugleft==1 && plugup==0) || (plugleft==0 && plugup==1)) { int tmp=s-plugleft*th[j]-plugup*th[j+1]; int trans=tmp+th[j]; dp[i][j+1][trans]+=dp[i][j][s]; trans=tmp+th[j+1]; dp[i][j+1][trans]+=dp[i][j][s]; } if ((plugleft==2 && plugup==0) || (plugleft==0 && plugup==2)) { int tmp=s-plugleft*th[j]-plugup*th[j+1]; int trans=tmp+th[j]*2; dp[i][j+1][trans]+=dp[i][j][s]; trans=tmp+th[j+1]*2; dp[i][j+1][trans]+=dp[i][j][s]; } if (plugleft==1 && plugup==1) { int sum=0,mat=M+1; for (int k=j+1;k<=M;k++) { int dig=checkbyte_three(s,k); if (dig==1) sum++; if (dig==2) sum--; if (sum==0) { mat=k; break; } } if (mat==M+1) continue; int trans=s-th[j]-th[j+1]-th[mat]; dp[i][j+1][trans]+=dp[i][j][s]; } if (plugleft==2 && plugup==2) { int sum=0,mat=-1; for (int k=j;k>=0;k--) { int dig=checkbyte_three(s,k); if (dig==1) sum--; if (dig==2) sum++; if (sum==0) { mat=k; break; } } if (mat==-1) continue; int trans=s-th[j]*2-th[j+1]*2+th[mat]; dp[i][j+1][trans]+=dp[i][j][s]; } if (plugleft==1 && plugup==2) continue; if (plugleft==2 && plugup==1) { int trans=s-th[j]*2-th[j+1]; dp[i][j+1][trans]+=dp[i][j][s]; } } } printf("%d\n",dp[N-1][M][1+2*th[M]]); } return 0; }
第四題:
題意:n堆石子,2人博弈.每次選擇一堆石子,移除至少一個(可以是任意多個)后,將剩下的任意分配(當然也可以什么都不做),2人輪流操作,不能操作者輸.求先手必勝還是必敗.
解:如果只有一堆石子,則先手必勝.如果有兩堆相同數目的石子,顯然先手必敗,因為對手只需對稱操作即可.同理可知,如果石子可分為兩組,對應堆石子數相等,如{1,2,3,1,2,3},則先手同樣必敗,於是數目相同的兩堆先可以暫不考慮.如果有兩堆數目不同的石子,則先手可以使之數目相同,先手必勝.如果有三堆互不相同的石子,先手可以選擇最大的一堆操作,移除一部分后,使剩下的石子正好補齊剩下兩堆之間的差距,先手必勝.依次類推,先手必敗當且僅當初始時石子數是{1,2,3,1,2,3}這種類型.

#include<stdio.h> int stack[15],C[15]; int main() { int N; while (scanf("%d",&N)!=EOF) { if (N==0) return 0; for (int i=1;i<=N;i++) scanf("%d",&C[i]); for (int i=1;i<=N-1;i++) for (int j=i+1;j<=N;j++) if (C[i]>C[j]) { int tmp=C[i]; C[i]=C[j]; C[j]=tmp; } int T=0; for (int i=1;i<=N;i++) { if (T==0) stack[++T]=C[i]; else { if (stack[T]==C[i]) T--; else stack[++T]=C[i]; } } if (T==0) printf("0\n"); else printf("1\n"); } return 0; }
第五題:
題意:給定一棵有N(N<=10000)個節點的樹,樹中的邊有一個權值W(W<=1001),定義dist(i,j)為從節點i到節點j的路徑上的邊的權值之和,求滿足dist(i,j)<=K的點對數目.
解:對於一棵有根樹而言,題目要求的點對之間的路徑有兩種可能,1.路徑不過根節點.2.路徑過根節點.第1種情況可以遞歸處理.針對於第2種情況,對每一個子孫節點記錄兩種屬性,1.到根節點的距離(depth).2.屬於根節點的哪個兒子的子孫(belong).可以分兩部分來考慮,第一部分:根節點作為端點,O(N)即可求出.第二部分:根不是端點,此種情況下點對的數量為根節點所有子孫中depth之和小於K的對數減去depth之和小於K且belong值相同的點的對數.通過排序可以以Nlog(N)的時間解決.為了減少搜索樹的深度,每次遞歸時可以用O(N)的時間找出樹的重心作為樹根進行計算.

#include<stdio.h> #include<stdlib.h> #include<string.h> #include<vector> #define GroupSize 10025 using namespace std; vector<int> G[GroupSize],E[GroupSize]; int Depth[GroupSize],Belong[GroupSize],hash[GroupSize],sum[GroupSize],maxv[GroupSize]; bool vis[GroupSize]; int N,K,Sts,T,Ans; int cmp0(const void * x,const void * y) { int Px=*(int *)x,Py=*(int *)y; if (Depth[Px]<Depth[Py]) return -1; else if (Depth[Px]==Depth[Py]) return 0; else return 1; } int cmp1(const void * x,const void * y) { int Px=*(int *)x,Py=*(int *)y; if (Belong[Px]<Belong[Py]) return -1; else if (Belong[Px]>Belong[Py]) return 1; else if (Depth[Px]<Depth[Py]) return -1; else if (Depth[Px]==Depth[Py]) return 0; else return 1; } void dfs(int Root,int father) { hash[++Sts]=Root; sum[Root]=1; maxv[Root]=0; for (int i=0;i<G[Root].size();i++) { int v=G[Root][i]; if (v==father || vis[v]) continue; dfs(v,Root); sum[Root]+=sum[v]; maxv[Root]=maxv[Root]>sum[v] ? maxv[Root]:sum[v]; } } int GetRoot(int Root,int father) { Sts=0; dfs(Root,father); int Cnt=sum[Root],Min=0x7FFFFFFF,Tr; for (int i=1;i<=Sts;i++) { int v=hash[i]; int tmp=maxv[v]>Cnt-sum[v] ? maxv[v]:Cnt-sum[v]; if (tmp<Min) { Min=tmp; Tr=v; } } return Tr; } void find(int Root,int father) { for (int i=0;i<G[Root].size();i++) { int v=G[Root][i]; if (v==father || vis[v]) continue; if (Depth[Root]+E[Root][i]<=K) { hash[++T]=v; Depth[v]=Depth[Root]+E[Root][i]; Belong[v]=Belong[Root]; find(v,Root); } } } void GetNear(int Root,int father) { T=0; hash[0]=Root; Depth[Root]=0; Belong[Root]=father; for (int i=0;i<G[Root].size();i++) { int v=G[Root][i]; if (v==father || E[Root][i]>K || vis[v]) continue; hash[++T]=v; Depth[v]=E[Root][i]; Belong[v]=v; find(v,Root); } } int CountAll() { int R=T,ans=0; for (int i=1;i<=T;i++) { while (Depth[hash[i]]+Depth[hash[R]]>K && R>=1) R--; ans+=R; if (R>=i) ans--; } ans/=2; for (int i=1;i<=T;i++) if (Depth[hash[i]]<=K) ans++; return ans; } int CountRepeat() { int L=1,R,Cur,ans=0; while (L<=T) { for (int i=L;i<=T;i++) if (i==T || Belong[hash[i]]!=Belong[hash[i+1]]) { Cur=R=i; break; } for (int i=L;i<=R;i++) { while (Depth[hash[i]]+Depth[hash[Cur]]>K && Cur>=L) Cur--; ans+=Cur-L+1; if (Cur>=i) ans--; } L=R+1; } return ans/2; } void solve(int Root,int father) { Root=GetRoot(Root,father); vis[Root]=true; GetNear(Root,father); qsort(&hash[1],T,sizeof(int),cmp0); Ans+=CountAll(); qsort(&hash[1],T,sizeof(int),cmp1); Ans-=CountRepeat(); for (int i=0;i<G[Root].size();i++) { int v=G[Root][i]; if (v==father || vis[v]) continue; solve(v,Root); } } int main() { while (scanf("%d%d",&N,&K)!=EOF) { if (N+K==0) return 0; for (int i=1;i<=N;i++) G[i].clear(); for (int i=1;i<=N;i++) E[i].clear(); for (int i=1;i<N;i++) { int x,y,c; scanf("%d%d%d",&x,&y,&c); G[x].push_back(y); G[y].push_back(x); E[x].push_back(c); E[y].push_back(c); } memset(vis,0,sizeof(vis)); Ans=0; solve(1,-1); printf("%d\n",Ans); } return 0; }
第六題
題意:n種硬幣,每種有A[i]個,面值分別為C[i]求在[1,m]之間能組成多少種不同的面值.
解:裝箱問題誰都會寫,但我原來寫的一直是三重循環,關鍵在於每種有A[i]個,從而將每種硬幣又循環了A[i]次.其實只需開一個數組p[v],表示達到v體積當前面值硬幣至少用多少枚,只需滿足p[v-C[i]]+1<=A[i]即可,從而砍掉一重循環,復雜度將為O(nm).

#include<stdio.h> #include<string.h> #define gs 100010 int A[100],C[100],p[gs]; bool v[gs]; int main() { int N,M; while (scanf("%d%d",&N,&M)!=EOF) { if (N+M==0) return 0; for (int i=1;i<=N;i++) scanf("%d",&A[i]); for (int i=1;i<=N;i++) scanf("%d",&C[i]); memset(v,false,sizeof(v)); v[0]=true; for (int i=1;i<=N;i++) { memset(p,0,sizeof(p)); for (int j=1;j<=M;j++) { if (v[j] || j<A[i]) continue; if (v[j-A[i]] && p[j-A[i]]<C[i]) { v[j]=true; p[j]=p[j-A[i]]+1; } } } int ans=0; for (int i=1;i<=M;i++) if (v[i]) ans++; printf("%d\n",ans); } return 0; }
第七題
題意:求最長不相交重復子串的長度,特殊之處在於只要變化規律相同便視為相同,比如1,2,3,4,5和6,7,8,9,10就視為相同.
解:將數列兩兩相鄰作差即可消除這特異之處.先用后綴數組的算法求出sa[],height[],然后二分長度,將height[]>k且連續的后綴划分為一組,求出同組內起始位置的最大距離,如果大於k說明存在長度為k的不相交重復子串.

#include<cstdio> #include<iostream> #include<algorithm> using namespace std; const int MAX = 20025; int num[MAX]; int sa[MAX], rank[MAX], height[MAX]; int wa[MAX], wb[MAX], wv[MAX], wd[MAX]; int N; int cmp(int *r, int a, int b, int l) { return r[a] == r[b] && r[a+l] == r[b+l]; } void Getsa(int *r, int n, int m) { int i, j, p, *x = wa, *y = wb, *t; for(i = 0; i < m; i ++) wd[i] = 0; for(i = 0; i < n; i ++) wd[x[i]=r[i]] ++; for(i = 1; i < m; i ++) wd[i] += wd[i-1]; for(i = n-1; i >= 0; i --) sa[-- wd[x[i]]] = i; for(j = 1, p = 1; p < n; j *= 2, m = p) { for(p = 0, i = n-j; i < n; i ++) y[p ++] = i; for(i = 0; i < n; i ++) if(sa[i] >= j) y[p ++] = sa[i] - j; for(i = 0; i < n; i ++) wv[i] = x[y[i]]; for(i = 0; i < m; i ++) wd[i] = 0; for(i = 0; i < n; i ++) wd[wv[i]] ++; for(i = 1; i < m; i ++) wd[i] += wd[i-1]; for(i = n-1; i >= 0; i --) sa[-- wd[wv[i]]] = y[i]; for(t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i < n; i ++) { x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p - 1: p ++; } } } void GetHeight(int *r, int n) // ?height??? { int i, j, k = 0; for(i = 1; i <= n; i ++) rank[sa[i]] = i; for(i = 0; i < n; height[rank[i ++]] = k) { for(k ? k -- : 0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; k ++); } } bool AC(int k) { int l=1,mx,mn,r; while (l<=N) { r=l; mx=mn=sa[l]; while (height[r+1]>=k && r<N) { r++; if (mx<sa[r]) mx=sa[r]; if (mn>sa[r]) mn=sa[r]; } if (mx-mn>k) return true; l=r+1; } return false; } int main() { freopen("input.txt","r",stdin); while (scanf("%d",&N)!=EOF) { int i; if (N==0) return 0; for ( i=0;i<N;i++) scanf("%d",&num[i]); if (N<10) { printf("0\n"); continue; } for ( i=0;i<N-1;i++) num[i]=num[i+1]-num[i]+100; N--; num[N] = 0; Getsa(num, N + 1, 200); GetHeight(num, N); int l=1,r=N,mid; while (l+1<r) { mid=(l+r)>>1; if (AC(mid)) l=mid; else r=mid; } if (l<4) printf("0\n"); else printf("%d\n",l+1); } return 0; }
第八題
題意:有n個人,第i個人要去fi樓.已知步行上下一層花費20秒,電梯上下一層花費4秒,電梯每次開門停10秒.求使所有人到達指定樓層所需花費的最小時間(電梯最初停在1樓).
解:又是一道二分答案題.采用貪心策略,對於指定的時間,按目標樓層從低到高遍歷每一個人.當一個人步行時間足夠時就讓他步行,否則必須升高電梯.貪心之處在於,電梯升高的位置應在使當前人時間足夠的情況下盡量高.這樣一來,要去比電梯升到之處更高的樓層的人所需時間固然更短,而在要去當前人目標樓層和電梯升到位置之間樓層的人下電梯后需向下步行,如果比他下的樓層更多的人時間都夠,他當然時間也夠.

#include<stdio.h> #define gs 30010 int f[gs]; int curf,curt,N; int abs(int x) { if (x<0) return -1*x; else return x; } bool judge(int T) { curf=1; curt=0; for (int j=1;j<=N;j++) { int i=f[j]; if ((i-1)*20<=T) continue; if (curt+abs(curf-i)*20<=T) continue; int tr=i-1; while (true) { int tmp=(curt+(curf==1 ? 0:10)+4*(tr+1-curf))+abs(tr+1-i)*20; if (tmp<=T) tr++; else break; } if (tr==i-1) return false; curt+=((curf==1 ? 0:10)+4*(tr-curf)); curf=tr; } return true; } int main() { while (scanf("%d",&N)!=EOF) { if (N==0) return 0; for (int i=1;i<=N;i++) scanf("%d",&f[i]); int l=-1,r=(f[N]-1)*20,mid; while (l+1<r) { int mid=(l+r)>>1; if (judge(mid)) r=mid; else l=mid; } printf("%d\n",r); } return 0; }