今天看了下男人八题,的确挺难的。看了这篇文章 http://www.cnblogs.com/dramstadt/p/3439725.html
转载如下。不过这个人的表述实在不太清楚。
第一题
题意: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#include #include using namespace std;#ifndef BIGNUM#define BIGNUMclass 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< =1;i--) if (d[i]!=ops.d[i]) return false; return true;}bool BigNum::operator > (const BigNum &ops) const{ if (Len ops.Len) return true; else { for (int i=Len;i>=1;i--) if (d[i] ops.d[i]) return true; } return false;}bool BigNum::operator < (const BigNum &ops) const{ if (Len ops.Len) return false; else { for (int i=Len;i>=1;i--) if (d[i] ops.d[i]) return false; } return false;}bool BigNum::operator >= (const BigNum &ops) const{ if (Len ops.Len) return true; else { for (int i=Len;i>=1;i--) if (d[i] ops.d[i]) return true; } return true;}bool BigNum::operator <= (const BigNum &ops) const{ if (Len ops.Len) return false; else { for (int i=Len;i>=1;i--) 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;}#endifBigNum C(int N,int K){ BigNum ret(1); for (int i=0;i
第二题
题意:若干堆石子,每次可以任意合并其中相邻的两堆,花费代价为两堆石子个数之和,求最小代价.
解:这题用动规谁都能5分钟写完代码(except the freshmans like me),关键n略大,达到50000.于是就要用到神奇的GarsiaWachs算法,朴素实现O(n^2),加平衡树优化为O(nlogn).不过这题由于数据原因,朴素即过(原文:You may assume the answer will not exceed 1000000000.)不过在看过fanhqme大牛的代码后发现,同样是朴素,我自己写就不一定能过,这就是人和人的差距啊...
#includeint A[50025],ret,N,T;void combine(int k){ int tmp=A[k]+A[k-1],j; ret+=tmp; for (int i=k;i 0 && A[j-1] =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 =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#include 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 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}这种类型.
#includeint 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#include #include #include #define GroupSize 10025using namespace std;vector 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] Belong[Py]) return 1; else if (Depth[Px] 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 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
第六题
题意:n种硬币,每种有A[i]个,面值分别为C[i]求在[1,m]之间能组成多少种不同的面值.
解:装箱问题谁都会写,但我原来写的一直是三重循环,关键在于每种有A[i]个,从而将每种硬币又循环了A[i]次.其实只需开一个数组p[v],表示达到v体积当前面值硬币至少用多少枚,只需满足p[v-C[i]]+1<=A[i]即可,从而砍掉一重循环,复杂度将为O(nm).
#include#include #define gs 100010int 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
第七题
题意:求最长不相交重复子串的长度,特殊之处在于只要变化规律相同便视为相同,比如1,2,3,4,5和6,7,8,9,10就视为相同.
解:将数列两两相邻作差即可消除这特异之处.先用后缀数组的算法求出sa[],height[],然后二分长度,将height[]>k且连续的后缀划分为一组,求出同组内起始位置的最大距离,如果大于k说明存在长度为k的不相交重复子串.
#include#include #include 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 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 >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#define gs 30010int 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 >1; if (judge(mid)) r=mid; else l=mid; } printf("%d\n",r); } return 0;}