【NOIP2006】提高组
T1能量项链
这一届的T1终于不是模拟题了,改成了一道环形dp。刚开始没有考虑好环形的细节导致WA了一半点(还是弱啊QAQ)。说说正解吧:f[l][r]表示把l到r之间的能量珠合成一颗所释放的最大能量,那么我们就可以通过枚举中间点k,f[l][r]=max(f[l][r],f[l][k]+he[l]*ta[k]*ta[r]+f[k+1][r])。但是由于是环形,我们还要把1-n再储存一遍,这样的话答案就是f[i][i+n-1](1<=i<=n)的最大值了。最后注意一下循环嵌套枚举的顺序就好了。
时间复杂度O(n3),很容易就跑过了。
代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int f[205][205],he[205],ta[205]; int main() { int n,mxa=0; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&he[i]); he[i+n]=he[i]; if(i==1)ta[n]=he[i],ta[i+n-1]=ta[n]; else ta[i-1]=he[i],ta[i+n-1]=he[i]; f[i][i]=0;f[i+n][i+n]=0; } for(int i=n*2-1;i>=1;i--){ for(int j=i+1;j<=i+n-1&&j<=n*2;j++){ for(int k=i;k<j;k++){ f[i][j]=max(f[i][j],f[i][k]+he[i]*ta[k]*ta[j]+f[k+1][j]); } } } for(int i=1;i<=n;i++) mxa=max(mxa,f[i][i+n-1]); printf("%d",mxa); return 0; }
T2金明的预算方案
其实这就是一道要分情况的01背包问题,决策变成了购买主件、购买主件和附件1、购买主件和附件2以及购买主件和两件附件4种。因为所有的价格都是10的倍数,所以可以把所有的钱数在一开始就除以10,到最后输出答案是再乘回去,这样能有效降低时间复杂度和空间复杂度。
代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int p1[65],p2[65],v[65],v1[65],v2[65],p[65]; bool ok[65]; int f[32001]; int main() { int n,vi,pi,qi,m,mxa=0; scanf("%d %d",&n,&m);n/=10; for(int i=1;i<=m;i++){ scanf("%d %d %d",&vi,&pi,&qi); vi/=10; if(qi){ ok[i]=1; if(!v1[qi]){v1[qi]=vi;p1[qi]=pi;} else {v2[qi]=vi;p2[qi]=pi;} } else{v[i]=vi;p[i]=pi;} } for(int i=1;i<=m;i++){ if(ok[i])continue; for(int j=n;j>=v[i];j--){ f[j]=max(f[j],f[j-v[i]]+v[i]*p[i]); if(j>=v[i]+v1[i])f[j]=max(f[j],f[j-v[i]-v1[i]]+v[i]*p[i]+v1[i]*p1[i]); if(j>=v[i]+v2[i])f[j]=max(f[j],f[j-v[i]-v2[i]]+v[i]*p[i]+v2[i]*p2[i]); if(j>=v[i]+v1[i]+v2[i])f[j]=max(f[j],f[j-v[i]-v1[i]-v2[i]]+v[i]*p[i]+v1[i]*p1[i]+v2[i]*p2[i]); mxa=max(mxa,f[j]); } } printf("%d",mxa*10); return 0; }
T3作业调度方案
这一年的模拟题放在了第三题,这道题传说也是NOIP中最考语文的题目(我看了几遍还是很懵,最后不得不上网看翻译才理解好题意)。抛开文字游戏不说,这道题其实不是很难,贪心的思路也在要求中说了出来,只需要按照顺序模拟即可。因为数据范围很弱,因此可以肆意用空间啦。开一个bool数组ok[i][j]表示第i台机器在第j秒时是否为空闲状态。我们需要把目前的操作放在这个工件前一个工序之后尽量靠前的位置,值得注意的是一个操作完成的那一刻即可马上开始下一个操作,即认为那一刻机器处于空闲状态。剩下的就是要注意打标记和消除标记的细节问题。
代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int q[505],ta=0,now[25]; int p[25][25],t[25][25],tt[25]; bool ok[25][505],f; int main() { int n,m,mx=0; scanf("%d %d",&m,&n); for(int i=1;i<=n;i++)now[i]=1; for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) scanf("%d",&q[++ta]); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&p[i][j]); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&t[i][j]); for(int i=1;i<=ta;i++){ int x=q[i],y=p[x][now[x]],t0=t[x][now[x]]; for(int j=tt[x];;j++){int j0=j; if(ok[y][j])continue;f=1; for(int k=j;k<j+t0;k++){ if(ok[y][k]){j=k;f=0;break;} ok[y][k]=1; } if(!f){for(int k=j0;k<j;k++)ok[y][k]=0;} else{tt[x]=j0+t0;mx=max(mx,tt[x]);break;} } now[x]++; } printf("%d",mx); return 0; }
T4 2^k进制数
这道题我一开始是很懵逼的,后来看了黄学长的题解和代码才幡然醒悟。在这里直接引用黄学长的题解:
题目中的那个从另一角度分析就已经蕴含了这个题的基本思路。就以题目的例子为例,长度为7位的01字串按3位一段就这样分:0 000 000。其中除了首段,每段都小于(111)2,也即小于2k,而首段自然是小于2w%k(对于w%k为0时也成立)了。
如果首段为0,则当这个2k进制数位数分别为2、3、…、[n/k]时,如果用b_max表示2k,对应的解的个数分别为C[b_max-1][2]、C[b_max-1][3]、…、C[b_max-1][n/k](C[i][j]表示从i个数里选j个构成一组组合)。
如果首段不为0,设首段为x,则解就有c[b_max-x-1][n/k]个。
这样,求解的个数就搞定了,剩下的活就是高精了。求组合数可以用这个公式:C[n][m]=C[n-1][m-1]+C[n-1][m],这样高精就只用加法了。
代码(基本就是黄学长的思路):
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int shu[205]; int c[520][520][105]; int k,w,wei; void plus1(int x[],int y[],int z[]) { z[0]=max(x[0],y[0]); for(int i=1;i<=z[0];i++){ z[i]+=x[i]+y[i]; z[i+1]+=z[i]/10; z[i]%=10; } if(z[z[0]+1]>0)z[0]++; } void plus2(int x[],int y[]) { x[0]=max(x[0],y[0]); for(int i=1;i<=x[0];i++){ x[i]+=y[i]; x[i+1]+=x[i]/10; x[i]%=10; } if(x[x[0]+1]>0)x[0]++; } int main() { scanf("%d %d",&k,&w); int bx=1<<k; int ax=1<<(w%k); for(int i=0;i<=bx;i++){ for(int j=0;j<=i;j++){ if(j==0)c[i][0][0]=c[i][0][1]=1; else plus1(c[i-1][j-1],c[i-1][j],c[i][j]); } } for(int i=2;i<=w/k&&i<bx;i++)plus2(shu,c[bx-1][i]); for(int i=1;i<ax&&i+w/k<bx;i++)plus2(shu,c[bx-i-1][w/k]); for(int i=shu[0];i>=1;i--)printf("%d",shu[i]); return 0; }