1001考试题解
发了题之后发现做过T1,还有一群大佬表示T2也做过……默默去看T2,几分钟之后整套题都被换掉了。读一读题,T2仿佛很怪,T3题意倒是清楚但是没什么思路,还是从T1开始做。T1一看第一感觉是有公式的,但是后来发现n^2仿佛没什么问题,随手写了个n^2的小DP。样例当然是能过,打算就放下,忽然想起来试试极限数据发现炸long long而且仿佛炸得理所当然。从此注意力转移:推式子->写高精,把原来递推的加法写了个高精,发现T->压位->还T->再压位,然后算了一下复杂度悲伤地领悟到这已经不是压位所能解决的问题了。高精和低精答案对比一下,发现低精过了30就已经炸掉了……高精大概压八位能撑到1000,后来就没有再写。正方形的路径是可以用卡特兰数的,但是这个怎么处理并不是很明白,也没再往组合方向想。T2看一遍不可做再看一遍还是不可做,是wq大佬所说的那种“非典型图论要找结论”题,操作太繁琐无从下手归纳,虽然后来证明有种种骗分的方法但是毕竟是看运气权当谈笑。T3暴力想法是dfs枚举平民身份,复杂度主要看m的大小感觉很玄,想过枚举贵族但是当时认为贵族和平民数量几乎一样并没有什么区别,带着对dfs一视同仁的复杂度分析始终没有尝试(当然我就算枚举贵族也多半只是按层挨个枚举而不会想到链上状压)。然后结果是T3全T了,T2完全是骗分,T1和我预期的差不多;并没有什么意料之外的感觉,考试的过程中思路推进得很慢,没有得到什么实质性的进展。
考试的最后总会想想,我这场考试过程中想到了什么呢?有时候答案是很不让人满意的。昨天晚上回宿舍之后也在想,教练很久以前仿佛说过“不要依赖题解,依赖题解上瘾”,最近可能看题解看得太多了,从数据结构2开始有很多完全不会的知识,李超线段树大概理解但是还是不会打,线段树维护hash会打了但是能想到吗?杂题专题也有很多巧妙的解法,自己想到的很少。前几天在生奥教室上自习的时候看到墙上挂着“万念归一,切勿水题”,当时我还觉得很好玩,生奥又不用网站做题,学了什么多半只有自己知道,有什么“水题”之说呢?OI对心态的要求大概比别的奥赛都要高吧。
网格
时间限制: 1 Sec 内存限制: 256 MB
【问题描述】
某城市的街道呈网格状,左下角坐标为A(0, 0),右上角坐标为B(n, m),其中n >= m。现在从A(0, 0)点出发,只能沿着街道向正右方或者正上方行走,且不能经过图示中直线左上方的点,即任何途径的点(x, y)都要满足x >= y,请问在这些前提下,到达B(n, m)有多少种走法.
【输入格式】
输入文件中仅有一行,包含两个整数n和m,表示城市街区的规模。
【输出格式】
输出文件中仅有一个整数和一个换行/回车符,表示不同的方案总数。
【输入样例1】
6 6
【输出样例1】
132
【输入样例2】
5 3
【输出样例2】
28
【数据范围】
50%的数据中,n = m,
在另外的50%数据中,有30%的数据:1 <= m < n <= 100
100%的数据中,1 <= m <= n <= 5 000
【题解】
一个公式,ans=C(n+m,m)-C(n+m,m-1),可以看作是从卡特兰数的公式C(2n,n)-C(2n,n-1)中推来的。这个问题还可以等价于问题2:有n个1和m个-1(n>=m),共n+m个数排成一列,满足对所有0<=k<=n+m的前k个数的部分和Sk >= 0的排列数。如果x不能等于y,或问题2中Sk不能等于0,那么结果是C(n+m-1,m)-C(n+m-1,m-1)。实现上筛一下素数分解质因数,高精乘低精处理组合数再高精减得答案。虽然卡特兰数可以O(n^2)递推得到,但还是慎重DP吧。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int sj=10005; int n,m,p[sj],ge,cnt[sj],zh1[sj],zh2[sj],temp,jw,mw1,mw; bool fs[sj]={1,1}; void getprime() { for(int i=2;i<=sj-5;i++) { if(!fs[i]) p[++ge]=i; for(int j=1;j<=ge&&p[j]*i<=sj-5;j++) { fs[p[j]*i]=1; if(i%p[j]==0) break; } } } void getzh(int x,int y,int z) { for(int i=2;i<=x;i++) { temp=i; for(int j=1;j<=ge;j++) { while(temp%p[j]==0) { cnt[j]++,temp/=p[j]; if(temp==1) break; } if(temp==1) break; } } for(int i=2;i<=y;i++) { temp=i; for(int j=1;j<=ge;j++) { while(temp%p[j]==0) { cnt[j]--,temp/=p[j]; if(temp==1) break; } if(temp==1) break; } } for(int i=2;i<=z;i++) { temp=i; for(int j=1;j<=ge;j++) { while(temp%p[j]==0) { cnt[j]--,temp/=p[j]; if(temp==1) break; } if(temp==1) break; } } zh2[1]=1,jw=0; for(int j=1;j<=ge;j++) for(int k=1;k<=cnt[j];k++) for(int l=1;l<=10000;l++) { zh2[l]*=p[j],zh2[l]+=jw; jw=zh2[l]/10,zh2[l]%=10; } } int main() { scanf("%d%d",&n,&m); getprime(); getzh(n+m,n,m); memset(cnt,0,sizeof(cnt)); memcpy(zh1,zh2,sizeof(zh2)); memset(zh2,0,sizeof(zh2)); getzh(n+m,n+1,m-1); jw=0; for(int i=1;i<=10000;i++) { zh2[i]+=jw; if(zh2[i]>zh1[i]) zh1[i]+=10,jw=1; else jw=0; zh1[i]-=zh2[i]; } mw=10000; while(zh1[mw]==0) mw--; printf("%d",zh1[mw]); if(mw==1) printf("\n"); for(int i=mw-1;i>1;i--) printf("%d",zh1[i]); if(mw!=1) printf("%d\n",zh1[1]); return 0; }
相框
时间限制: 1 Sec 内存限制: 256 MB
【问题描述】
P大的基础电路实验课是一个无聊至极的课。每次实验,T君总是提前完成,管理员却不让T君离开,T君只能干坐在那儿无所事事。
先说说这个实验课,无非就是把几根导线和某些元器件(电阻、电容、电感等)用焊锡焊接起来。
为了打发时间,T君每次实验做完后都在焊接一些诡异的东西,这就是他的杰作:
T君不满足于焊接奇形怪状的作品,强烈的破坏欲驱使他拆掉这个作品,然后将之焊接成规整的形状。这会儿,T君正要把这个怪物改造成一个环形,当作自己的相框,步骤如下:
T君约定了两种操作:
1. 烧熔一个焊点:使得连接在焊点上的某些导线相分离或保持相连(可以理解为:把焊点上的导线划分为若干个类,相同类中的导线相连,不同类之间的导线相离)
2. 将两根导线的自由端(即未与任何导线相连的一端)焊接起来。
例如上面的步骤中,先将A点烧熔,使得导线1与导线2、4点分离;再将D点烧熔,使得4、5与3、7相离;再烧熔E,使7与6、8相离;最后将1、7相连。
T君想用最少的操作来将原有的作品改造成为相框(要用上所有的导线)。
【输入文件】
输入文件的第一行共有两个整数n和m:分别表示原有的作品的焊点和导线的数量 (0 ≤ n ≤ 1 000, 2 ≤ m ≤ 50 000)。焊点的标号为1~n。
接下来的m行每行共有两个整数:导线两端所连接的两个焊点的标号,若不与任何焊点相连,则将这一端标号为0。
原有的作品可能不是连通的。
某些焊点可能只有一根导线与之相连,在该导线的这一端与其他导线相连之前,这些焊点不允许被烧熔。
某些焊点甚至没有任何导线与之相连,由于T君只关心导线,因此这些焊点可以不被考虑。
【输出文件】
输出文件只包含一个整数:表示T君需要将原有的作品改造成相框的最少步数。
【输入样例1】
6 8
1 2
1 3
3 4
1 4
4 6
5 6
4 5
1 5
【输出样例1】
4
【输入样例2】
0 2
0 0
0 0
【输出样例2】
2
【输入样例3】
3 3
0 1
0 0
2 2
【输出样例3】
4
【数据规模和约定】
30%的数据中n≤10;
100%的数据中n≤1000。
【题解】
刚开始觉得自由端很难办,但是真正解决的时候只要把它当作新的点来看就可以了。题目中说得很复杂,归根到底是要得到一个环,环上的点度一定为2。把所有度大于2的点分奇偶性断开,偶数断成很多个2,奇数断成很多个2和一个1。用并查集维护一下图的连通性,如果只有一个连通块,只要把1两两接起来就会出环了。如果有多个联通块,需要把每个连通块拆成链,那么每个联通块里需要有且只有两个度为1的点;如果之前断过它,可以在断的同时拆出两个度为1的点来,否则还要专门为了拆出1再断一个点,这样自环的问题无形之中也被解决了;最后需要把链接起来,对答案的贡献即为连通块个数。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int sj=110000; int n,m,cnt,a1,a2,f[sj],d[sj],ge,ans,js[sj]; bool cut[sj],cx[sj]; int find(int x) { if(f[x]==-1) return x; return f[x]=find(f[x]); } void hb(int x,int y) { x=find(x),y=find(y); if(x!=y) f[y]=x; } int main() { scanf("%d%d",&n,&m); memset(f,-1,sizeof(f)); cnt=n; for(int i=1;i<=m;i++) { scanf("%d%d",&a1,&a2); if(a1==0) a1=++cnt; if(a2==0) a2=++cnt; d[a1]++,d[a2]++; hb(a1,a2); cx[a1]=cx[a2]=1; } for(int i=1;i<=cnt;i++) if(cx[i]&&find(i)==i) ge++; if(ge==1) { a1=0; for(int i=1;i<=cnt;i++) { if(!cx[i]) continue; if(d[i]%2==1) a1++; if(d[i]>2) ans++; } ans+=(a1>>1); } if(ge!=1) { a1=0; for(int i=1;i<=cnt;i++) { if(!cx[i]) continue; if(d[i]>2) ans++,cut[find(i)]=1; if(d[i]%2==1) js[find(i)]++; } for(int i=1;i<=cnt;i++) if(find(i)==i) { if(!cx[i]) continue; if(js[i]>2&&cut[i]) ans+=(js[i]-2)/2; if(js[i]==0&&!cut[i]) ans++; } ans+=ge; } printf("%d",ans); return 0; }
战争调度
时间限制: 2 Sec 内存限制: 128 MB
【题解】
和我预估的全不一样,枚举贵族和枚举平民复杂度相差很大。昨天晚上大概也是这个时候在51nod上看到一道题,标签是“树DP”、“状压DP”,然后我问wzz大佬“你做过树上的状压吗?”,今天考了这样一道十分应景的题……
状压DP方面,只能纵向压,用二进制表示这一点到根节点路径上的点的状态。树DP方面,f[i][j]表示以i为根的子树中有j个平民作战。到了平民那一层所有祖先的状态都已经被确定了,所以答案也是确定的。对于每一个贵族则向下dfs后枚举左右子树各有多少平民作战。最后选f[1][0~m]中最大的作为答案。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int sj=600; int n,m,size[sj<<1],w[sj][12],f[sj][12],ans,ef[12],g[sj<<1][sj],dy; int read() { int jg=0,jk=getchar()-'0'; if(jk<=9&&jk>0) jg=jk; jk=getchar()-'0'; while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; return jg; } void bj(int &x,int y) { x=x>y?x:y; } void dfs(int x,int tp) { if(x<ef[n-1]) { dfs(x<<1,tp<<1|1),dfs(x<<1|1,tp<<1|1); size[x]=size[x<<1]+size[x<<1|1]; memset(g[x],0,sizeof(g[x])); for(int i=0;i<=m&&i<=size[x<<1];i++) for(int j=0;j+i<=m&&j<=size[x<<1|1];j++) bj(g[x][i+j],g[x<<1][i]+g[x<<1|1][j]); dfs(x<<1,tp<<1),dfs(x<<1|1,tp<<1); for(int i=0;i<=m&&i<=size[x<<1];i++) for(int j=0;j+i<=m&&j<=size[x<<1|1];j++) bj(g[x][i+j],g[x<<1][i]+g[x<<1|1][j]); } else { dy=x-ef[n-1]+1; g[x][1]=g[x][0]=0; for(int i=1;i<n;i++) { if(tp&(1<<(i-1))) g[x][1]+=w[dy][n-i]; else g[x][0]+=f[dy][n-i]; } size[x]=1; } } int main() { ef[0]=1; for(int i=1;i<=10;i++) ef[i]=ef[i-1]*2; n=read(),m=read(); for(int i=1;i<=ef[n-1];i++) for(int j=n-1;j>=1;j--) w[i][j]=read(); for(int i=1;i<=ef[n-1];i++) for(int j=n-1;j>=1;j--) f[i][j]=read(); dfs(1,0); for(int i=0;i<=m;i++) if(g[1][i]>ans) ans=g[1][i]; printf("%d",ans); return 0; }