NOIP2000TG T2 乘积最大
题意:
将$N(6<=N<=40)$位的数字,用$K(1<=K<=6)$个乘号隔开,求最大积。
程序1(0pt):
看到分割/合并,考虑区间DP,思考一下,因为区间个数有限制,不能套区间DP乱搞。
因为乘法满足结合律,考虑将“摸到第$i$位”和“在这一位后放置第$j$个乘号”作为两个维度进行DP,记录最大值,设为$f_{i,j}$。
首先设给定数串为$a$,取其一部分的函数为$cut(a,l,r)$,发现从高位和低位开始取是一样的,为了简化,就和高精度一样,以最低位为第$1$位。
每次用第一个乘号时产生初值,每次从$j-1$的状态里转移。
同时,因为只能从计算过的状态转移才有意义,并且为了避免做高精乘法的时候出现不可知的奇怪错误,所以考虑在实现DP的时候维护这个访问值,设为$v$。
考虑寻找上一个节点的位数$p$满足
①$p>=j-1$,因为这里要放置第$j-1$个乘号,至少为第$j-1$位
②$p<i$,从前往后转移
③$f_{p,j-1}必须被访问过$
于是我们有
$$f_{i,j}=\mathop{max}\limits_{p=j-1}^{i-1}\{([v_{p,j-1}]*f_{p,j-1}*cut(a,p+1,i)\}$$
跑完$f_{i}$之后,显然$f_{i,k}$就是答案候选了。
然而有一个问题,就是读入。遇到这种数字和字串混合的输入很容易被爆。第一次提交的时候就因为读入爆〇了。好尴尬啊。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; typedef long long LL; const int N=40; const int M=6; struct num{ int s[N+3],l; num(int x){ memset(s,0,sizeof s); if(x==0){ l=1; return ; } l=0; while(x>0){ s[++l]=x%10; x/=10; } } num(){} int operator[](int i){ return s[i]; } }; void prt(num a){ for(int i=a.l;i>=1;i--) putchar(a[i]+'0'); } num operator*(num a,num b){ num c=num(0); for(int i=1;i<=a.l;i++) for(int j=1;j<=b.l;j++) c.s[i+j-1]+=a[i]*b[j]; c.l=a.l+b.l+1; for(int i=1;i<c.l;i++){ c.s[i+1]+=c[i]/10; c.s[i]%=10; } while(c[c.l]==0) c.l--; return c; } bool operator<(num a,num b){ if(a.l!=b.l) return a.l<b.l; for(int i=a.l;i>=1;i--) if(a[i]!=b[i]) return a[i]<b[i]; return false; } num max(num a,num b){ if(a<b) return b; return a; } num cut(num a,int l,int r){ num b(0); b.l=r-l+1; for(int i=1;i<=b.l;i++) b.s[i]=a[l+i-1]; return b; } num a; int n,k; num f[N+3][M+3],ans[N+3]; bool v[N+3][M+3]; int main(){ scanf("%d%d",&n,&k);getchar(); char ch; a.l=n; for(int i=n;i>=1;i--){ ch=getchar(); a.s[i]=ch-'0'; } memset(v,0,sizeof 0); for(int i=1;i<n;i++){ f[i][1]=cut(a,1,i); v[i][1]=true; for(int j=2;j<=k;j++) for(int p=j-1;p<i;p++) if(v[p][j-1]){ v[i][j]=1; f[i][j]=max(f[i][j] ,f[p][j-1]*cut(a,p+1,i)); } if(v[i][k]) ans[i]=f[i][k]*cut(a,i+1,n); num maxa(0); for(int i=1;i<n;i++) if(v[i][k]) maxa=max(maxa,ans[i]); prt(maxa); return 0; }
程序2(100pt):
思考了一下,应该是$scanf$和$getchar$混在同一行,那个回车不知何去何从导致的。其实我现在还没太搞明白个中机制。
考虑用模式串把回车收起来。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; typedef long long LL; const int N=40; const int M=6; struct num{ int s[N+3],l; num(int x){ memset(s,0,sizeof s); if(x==0){ l=1; return ; } l=0; while(x>0){ s[++l]=x%10; x/=10; } } num(){} int operator[](int i){ return s[i]; } }; void prt(num a){ for(int i=a.l;i>=1;i--) putchar(a[i]+'0'); } num operator*(num a,num b){ num c=num(0); for(int i=1;i<=a.l;i++) for(int j=1;j<=b.l;j++) c.s[i+j-1]+=a[i]*b[j]; c.l=a.l+b.l+1; for(int i=1;i<c.l;i++){ c.s[i+1]+=c[i]/10; c.s[i]%=10; } while(c[c.l]==0) c.l--; return c; } bool operator<(num a,num b){ if(a.l!=b.l) return a.l<b.l; for(int i=a.l;i>=1;i--) if(a[i]!=b[i]) return a[i]<b[i]; return false; } num max(num a,num b){ if(a<b) return b; return a; } num cut(num a,int l,int r){ num b(0); b.l=r-l+1; for(int i=1;i<=b.l;i++) b.s[i]=a[l+i-1]; return b; } num a; int n,k; num f[N+3][M+3],ans[N+3]; bool v[N+3][M+3]; int main(){ scanf("%d%d\r",&n,&k); char ch; a.l=n; for(int i=n;i>=1;i--){ ch=getchar(); a.s[i]=ch-'0'; } memset(v,0,sizeof 0); for(int i=1;i<n;i++){ f[i][1]=cut(a,1,i); v[i][1]=true; for(int j=2;j<=k;j++) for(int p=j-1;p<i;p++) if(v[p][j-1]){ v[i][j]=1; f[i][j]=max(f[i][j] ,f[p][j-1]*cut(a,p+1,i)); } if(v[i][k]) ans[i]=f[i][k]*cut(a,i+1,n); num maxa(0); for(int i=1;i<n;i++) if(v[i][k]) maxa=max(maxa,ans[i]); prt(maxa); return 0; }
小结:
要命的字符串搞法……