写两个题解,以示刷DP的重要性
HDOJ5464
题意:给定n和p,和n个整数,求选择任意k个数的和为p的倍数,k可以为0
很简单的想法:dp[i][j]为选前i个数,模p余数为j的方法数,那么dp[n][0]即为答案
细节:j的范围,每个数的大小只需保留-p到p内的值就好了
状态转移根据定义很好想的
int main(){
//input;
int i,j,k;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&p);
for(i=1;i<=n;i++){
scanf("%d",&num[i]);
num[i]%=p;
}
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(i=1;i<=n;i++)
for(j=0;j<p;j++)
dp[i][j]+=(dp[i-1][j]+dp[i-1][(j-num[i]+p)%p])%mod;
/*for(i=1;i<=n;i++){
for(j=0;j<p;j++) printf("%d ",dp[i][j]);
cout<<endl;
}*/
printf("%d\n",dp[n][0]);
}
return 0;
}
2015合肥网赛第9题:HDOJ5492
想了两个小时,公式也是对的:实力推:
思路如下:把方差的公式列出来,然后左右同乘(n+m-1)的平方,然后就把平均值化成了求和,,,,剩下的就是怎么DP了
先说自己错误的思路:
Sqr【i】【j】为从(1,1)到(i,j)的平方和
Sum【i】【j】为从(1,1)到(i,j)的数和
然后Dp【i】【j】就是之前维护的公式的值:::关键是这样水样例过了!
int t,n,m,avg,Min,mul;
ll Num[maxn][maxn];
ll Sum[maxn][maxn];
ll Sqr[maxn][maxn];
ll Dp[maxn][maxn];
void DP(){
int i,j;
memset(Dp,0,sizeof(Dp));
memset(Sum,0,sizeof(Sum));
memset(Sqr,0,sizeof(Sqr));
Sum[1][1]=Num[1][1];
Sqr[1][1]=Num[1][1]*Num[1][1];
Dp[1][1]=mul*Sqr[1][1]-Sum[1][1]*Sum[1][1];
for(i=2;i<=n;i++){
Sum[i][1]=Sum[i-1][1]+Num[i][1];
Sqr[i][1]=Sqr[i-1][1]+Num[i][1]*Num[i][1];
Dp[i][1]=Sqr[i][1]*mul-Sum[i][1]*Sum[i][1];
}
for(i=2;i<=m;i++){
Sum[1][i]=Sum[1][i-1]+Num[1][i];
Sqr[1][i]=Sqr[1][i-1]+Num[1][i]*Num[1][i];
Dp[1][i]=Sqr[1][i]*mul-Sum[1][i]*Sum[1][i];
}
for(i=2;i<=n;i++)
for(j=2;j<=m;j++){
ll Sum1,Sum2,Dp1,Dp2,Sqr1,Sqr2;
Sum1=Sum[i][j-1]+Num[i][j];
Sqr1=Sqr[i][j-1]+Num[i][j]*Num[i][j];
Dp1=Sqr1*mul-Sum1*Sum1;
Sum2=Sum[i-1][j]+Num[i][j];
Sqr2=Sqr[i-1][j]+Num[i][j]*Num[i][j];
Dp2=Sqr2*mul-Sum2*Sum2;
if (Dp1<Dp2){
Dp[i][j]=Dp1;
Sum[i][j]=Sum1;
Sqr[i][j]=Sqr1;
}
else{
Dp[i][j]=Dp2;
Sum[i][j]=Sum2;
Sqr[i][j]=Sqr2;
}
}
}
int main(){
//input;
int i,j;
scanf("%d",&t);
for(int Case=1;Case<=t;Case++){
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
scanf("%I64d",&Num[i][j]);
mul=n+m-1;
DP();
printf("Case #%d: %I64d\n",Case,Dp[n][m]);
}
return 0;
}
然而WA了一万次的原因是:
少一维不满足无后效性了,走过的格子的和是影响结果的
也许f[i-1][j]的时候和是a比和是b的结果要好
但是 f[i][j]的时候和 a+x[i][j]就比和是b+x[i][j]的结果要差了
重要的话说三遍:谢谢我毛毛雨巨巨,谢谢我毛毛雨巨巨,谢谢我毛毛雨巨巨!
所以,正确姿势应该是,
f[i][j][k]表示走到i,j这个点,且经过的点权值和为k的情况下,权值的平方和最小为f[i][j][k]
他应该由 f[i-1][j] [k - a[i][j]] 和 f[i][j-1][k-a[i][j]] 转移来
这样就很容易AC了!
公式一样,思路一样,DP实现不同,差距就是这么大,下午几个小时的方向没有白费,下面贴代码:
int a[40][40];
int sum[100],n,m;
int f[40][40][1900];
int main(){
//input;
int t;
scanf("%d",&t);
for(int Case=1;Case<=t;Case++){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
memset(f,0x7f7f7f7f,sizeof(f));
memset(sum,0,sizeof(sum));
f[1][1][a[1][1]]=a[1][1]*a[1][1];
sum[0]=0;
for(int i=1;i<=m;i++)
sum[i]=sum[i-1]+a[1][i];
for(int i=2;i<=m;i++)
f[1][i][sum[i]]=f[1][i-1][sum[i-1]]+a[1][i]*a[1][i];
memset(sum,0,sizeof(sum));
sum[0]=0;
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+a[i][1];
for(int i=2;i<=n;i++)
f[i][1][sum[i]]=f[i-1][1][sum[i-1]]+a[i][1]*a[i][1];
for(int i=2;i<=n;i++)
for(int j=2;j<=m;j++){
for(int k=0;k<=1800;k++){
if (f[i][j-1][k]<=1000000)
f[i][j][k+a[i][j]]=min(f[i][j][k+a[i][j]],f[i][j-1][k]+a[i][j]*a[i][j]);
if (f[i-1][j][k]<=1000000)
f[i][j][k+a[i][j]]=min(f[i][j][k+a[i][j]],f[i-1][j][k]+a[i][j]*a[i][j]);
}
}
int ans=0x7f7f7f7f;
for(int i=0;i<=1800;i++)
if (f[n][m][i]<=1000000)
ans=min(ans,(n+m-1)*f[n][m][i]-i*i);
printf("Case #%d: %d\n",Case,ans);
}
return 0;
}
其中1000000是为了防止0x7f7f7f7f乘法溢出的
——————————————————————————————————————————————————————————————————
重要的事情说三遍:
国庆需要好好练习!
国庆需要好好练习!
国庆需要好好练习!