真的报警啦,hzwer又出一堆丧题虐人啦.....
-------------------------------------------
A.[poj-1190]生日蛋糕
要做一个m层的蛋糕,每一层有高度和半径,且要分别比它上面的那一层的高度和半径大至少一,给定总体积n,求最小的侧面和顶上的面积之和m<=20,n<=10000
搜索....但是要加上比较强的剪枝。
1.如果此时的半径和高度无法建出剩余体积那么大的蛋糕,剪掉。这种情况我们不考虑半径和高度的减小,直接用((r-1)^2+(h-1))乘以剩下的层数对比就行了。
2.如果此时剩下的体积最少需要的面积加上此时已经搜到的面积比答案大,剪掉。因为面积和体积关于半径有不同次数的关系,所以我们要尽可能让半径大,整理之后可以得到:如果c+2*Sleft/r>ans,那么剪枝
复杂度O(能过)
#include<iostream> #include<cstdio> #define INF 2000000000 using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int ans=INF,n,m; inline int sqr(int x){return x*x;} void dfs(int xx,int s,int c,int lastr,int lasth) { if(s<0||c>ans)return;//cout<<"dfs"<<xx<<" "<<s<<" "<<c<<" "<<lastr<<" "<<lasth<<endl; if(!xx){if(s==0)ans=min(ans,c);return;} if(xx*sqr(lastr-1)*(lasth-1)<s)return; for(int i=lastr-1;i>=xx;i--) for(int j=lasth-1;j>=xx;j--) if(c+2*s/i<ans) dfs(xx-1,s-sqr(i)*j,c+2*i*j,i,j); } int main() { n=read();m=read(); for(int i=100;i;i--) for(int j=1000;j;j--) dfs(m-1,n-sqr(i)*j,i*2*j+i*i,i,j); printf("%d",ans==INF?0:ans); return 0; }
B.[百练4119]复杂的整数划分问题
多组数据,每次输入n和k,(n,k<=50)对于每组测试数据,输出以下三行数据:
第一行: N划分成K个正整数之和的划分数目
第二行: N划分成若干个不同正整数之和的划分数目
第三行: N划分成若干个奇正整数之和的划分数目
题解:乱dp呗。n^4
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int f1[55][55][55],f2[55][55][55],f3[55][55][55]; int n,K,num1[55],num2[55],num[55][55]; int main() { f1[0][0][0]=f2[0][0][0]=f3[0][0][0]=1; for(int k=1;k<=50;k++) for(int i=1;i<=50;i++) for(int j=1;j<=i;j++) { for(int l=0;l<=j;l++) { f1[k][i][j]+=f1[k-1][i-j][l]; if(l!=j) f2[k][i][j]+=f2[k-1][i-j][l]; if(j&1) f3[k][i][j]+=f3[k-1][i-j][l]; } num1[i]+=f2[k][i][j];num2[i]+=f3[k][i][j];num[k][i]+=f1[k][i][j]; } while(scanf("%d %d",&n,&K)!=EOF) printf("%d %d %d ",num[K][n],num1[n],num2[n]); return 0; }
C.[百练1191]棋盘分割
题目:将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了(n-1)次后,连同最后剩下的矩形棋盘共有n块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行) 原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成n块矩形棋盘,并使各矩形棋盘总分的均方差最小。
n<=15
题解:dp,用f[z][i][j][k][l]表示i-j行k-l列那个矩形割z次最小的答案。
复杂度8^4*n
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<iomanip> #define INF 2000000000 using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n; double s[15][15]; double ave; double ss[15][15][15][15][15]; double getans(int xfrom,int xto,int yfrom,int yto) { double h=0; for(int i=xfrom;i<=xto;i++) h+=s[i][yto]-s[i][yfrom-1]; return (h-ave)*(h-ave); } int main() { n=read(); for(int i=1;i<=8;i++) { s[i][0]=0; for(int j=1;j<=8;j++) { double x=read(); ave+=x; s[i][j]=s[i][j-1]+x; } } ave/=(double)n; for(int i=1;i<=8;i++) for(int j=i;j<=8;j++) for(int k=1;k<=8;k++) for(int l=1;l<=8;l++) ss[0][i][j][k][l]=getans(i,j,k,l); for(int kk=1;kk<=n-1;kk++) { for(int i=1;i<=8;i++) for(int j=i;j<=8;j++) for(int k=1;k<=8;k++) for(int l=k;l<=8;l++) { ss[kk][i][j][k][l]=INF; for(int x=i;x<=j-1;x++) { ss[kk][i][j][k][l]=min(ss[kk][i][j][k][l],ss[kk-1][i][x][k][l]+ss[0][x+1][j][k][l]); ss[kk][i][j][k][l]=min(ss[kk][i][j][k][l],ss[0][i][x][k][l]+ss[kk-1][x+1][j][k][l]); } for(int y=k;y<=l-1;y++) { ss[kk][i][j][k][l]=min(ss[kk][i][j][k][l],ss[kk-1][i][j][k][y]+ss[0][i][j][y+1][l]); ss[kk][i][j][k][l]=min(ss[kk][i][j][k][l],ss[0][i][j][k][y]+ss[kk-1][i][j][y+1][l]); } } } double ans=ss[n-1][1][8][1][8]/(double)n; ans=sqrt(ans); printf("%0.3lf",ans); return 0; }
D.[百练1724]ROADS
给定一个n个点m条边的图(n<=100,m<=10000),每条边有一定的权值及费用(均<=100)。给定c,求费用不超过c时1到n的最短路长度。
题解:很显然你最多只会走n-1条边,最短路的长度不会超过10000,那么用f[i][j]表示到第i个点,路径长度为k的最小费用。
枚举路径长度(1-10000),枚举边,更新f数组就行了。
oj机子跑得慢,最好优化一下233
复杂度MAXL*m
#include<iostream> #include<cstdio> #include<queue> #include<algorithm> #define INF 2000000000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int k,n,m; struct edge{ int from,to,w,c; }e[10005]; bool cmp(edge x,edge y){return x.w<y.w;} int f[105][10005]; int main() { k=read();n=read();m=read(); for(int i=1;i<=m;i++) {e[i].from=read();e[i].to=read();e[i].w=read();e[i].c=read();} sort(e+1,e+m+1,cmp); for(int i=1;i<=n;i++)for(int j=0;j<=10000;j++) f[i][j]=INF; f[1][0]=0; for(int i=1;i<=10000;i++) { for(int j=1;j<=m;j++) if(i>=e[j].w) { f[e[j].to][i]=min(f[e[j].to][i],f[e[j].from][i-e[j].w]+e[j].c); }else break; if(f[n][i]<=k)return 0*printf("%d ",i); } puts("-1"); return 0; }
E.[poj1037]A Decorative fence
给定n和k,求字典序第k小的n的波浪形全排列 n<=20,k是longlong
波浪形全排列:比如13254,任意连续的三个数字中中间会比两边都大或者小。
题解:我们发现一个1-n的全排列确定一项后剩余部分可以看作一个的1到n-1的全排列
用f1[i][j]表示1-i的全排列,第j个开头且先上升的波浪形全排列的个数,同理f2[i][j]表示先下降的,随意dp一下。
最后把每一位数字确定下来。复杂度大概是n^3吧
#include<iostream> #include<cstdio> #include<cstring> #define ll long long using namespace std; inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int n; ll K; ll f1[25][25],f2[25][25]; bool b[25]; int ans[25]; void solve(int xx) { if(xx>n)return;//cout<<"dfs"<<xx<<" "<<K<<endl; int num=n-xx+1,nown=0,i;ll tot=0; for(i=1;i<=n;i++) if(!b[i]) { ++nown;if(xx==2)tot+=i>ans[1]?f1[num][nown]:f2[num][nown]; else if(i>ans[xx-1]&&ans[xx-1]<ans[xx-2])tot+=f1[num][nown]; else if(i<ans[xx-1]&&ans[xx-1]>ans[xx-2])tot+=f2[num][nown]; if(tot>=K)break;else {K-=tot;tot=0;} } b[i]=1;ans[xx]=i;printf("%d ",i);solve(xx+1); } int main() { int t=read(); f1[1][1]=f2[1][1]=1; for(int l=2;l<=20;l++) for(int i=1;i<=l;i++) { for(int j=0;j<i;j++)f1[l][i]+=f2[l-1][j]; for(int j=l+1;j>i;j--)f2[l][i]+=f1[l-1][j-1]; } while(t--) { n=read();K=read();memset(b,0,sizeof(b)); for(int i=1;i<=n;i++) if(f1[n][i]+f2[n][i]>=K){b[i]=1;ans[1]=i;printf("%d ",i);solve(2);break;} else K-=f1[n][i]+f2[n][i]; puts(""); } }
F.[poj1390]Blocks
题意:有n(n<=200)个1-n的颜色的盒子组成的序列,你每次可以消除一个连续的相同颜色的盒子,得分为数量的平方
给一个解释⬆️
求可能的最大得分
题解:这道题dp的思想非常的妙。
先把相同颜色的连续段缩起来,设len[i]表示第i段的个数。
用f[i][j][k]表示i-j这个区间的盒子,第j个盒子和后面和它颜色相同的k个盒子一起消除能得到的最大得分,那么f[i][j][k]=f[i][j-1][0]+(len[j]+k)^2
然后我们每次处理一个状态时也考虑它和前面的相同颜色盒子一起消除的情况,即枚举一个z,i<=z<j且color[z]=color[j],
那么f[i][j][k]=max(f[i][j][k],f[i][z][k+len[j]]+f[z+1][j-1][0])
复杂度很玄学,理论上不能过,但是就是能过,所以就算O(能过)吧。
#include<iostream> #include<cstdio> #include<cstring> #define INF 2000000000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int s[205],len[205]; int f[205][405][205]; int n,tot; inline int sqr(int x){return x*x;} int main() { int t=read(); for(int qq=1;qq<=t;qq++) { tot=n=read();memset(f,0,sizeof(f)); for(int i=1;i<=n;i++)s[i]=read(); int j=0; for(int i=1;i<=n;i++) if(s[i]==s[i-1]) ++len[j]; else s[++j]=s[i],len[j]=1; n=j; for(int k=1;k<=n;k++) for(int i=1;i+k-1<=n;i++) { int j=i+k-1; for(int l=0;l<=tot-j;l++) { f[i][j][l]=max(f[i][j][l],f[i][j-1][0]+sqr(l+len[j])); for(int z=i;z<j;++z)if(s[z]==s[j]) f[i][j][l]=max(f[i][j][l],f[i][z][l+len[j]]+f[z+1][j-1][0]); } } printf("Case %d: %d ",qq,f[1][n][0]); } return 0; }
G.[bzoj3172]单词
有一篇文章由n个单词组成,求每个单词在文章中出现的个数。n<=200,总长度<=10^6
一股清流,居然有一道ac自动机模板题....
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int out[1000005]; int q[1000005],top=0,tail=0; char s[205][1000005]; int c[1000005][26]; int fail[1000005]; int n,cnt=1; int nn[1000005]; int check(int num) { int t=1,ans=0; for(int i=1;s[num][i];++i) { int to=s[num][i]-'a'; while(!c[t][to])t=fail[t]; t=c[t][to]; } return nn[t]; } void buildfail() { q[top]=1; while(top>=tail) { int x=q[tail++]; for(int i=0;i<26;i++) { if(!c[x][i]) continue; int t=fail[x]; while(!c[t][i])t=fail[t]; fail[c[x][i]]=c[t][i];out[c[t][i]]++;q[++top]=c[x][i]; } } top=-1;tail=0; for(int i=1;i<=cnt;i++)if(!out[i]) q[++top]=i; while(top>=tail) { int x=q[tail++];out[fail[x]]--;nn[fail[x]]+=nn[x]; if(!out[fail[x]])q[++top]=fail[x]; } } void ins(int num) { int t=1; for(int i=1;s[num][i];i++) { int to=s[num][i]-'a'; if(!c[t][to]) c[t][to]=++cnt; t=c[t][to]; nn[t]++; } } int main() { n=read(); for(int i=0;i<26;i++)c[0][i]=1;fail[1]=0; for(int i=1;i<=n;i++)scanf("%s",s[i]+1); for(int i=1;i<=n;i++)ins(i);buildfail(); for(int i=1;i<=n;i++)printf("%d ",check(i)); return 0; }
除了sb题全是dp,干脆叫dp模拟赛算了。