Problem 0537 : Bingo
解法
単純なDPだとTLE.
int n,m,s,dp[2][2001][3001]; int main() { while(scanf("%d%d%d",&n,&m,&s),n){ rep(i,m+1)rep(j,s+1)dp[0][i][j]=0; dp[0][0][s]=1; m=min(m,s); int cur=0,next=1; rep(i,n*n){ rep(j,m+1)rep(k,s+1)dp[next][j][k]=0; for(int j=i;j<=m;j++) for(int k=j*(n*n-i);k<=s;k++)if(dp[j][k]) { for(int l=j+1;l<=m;l++){ if(k<l)break; dp[next][l][k-l]=(dp[next][l][k-l]+dp[cur][j][k])%mod; if(l*(n*n-i)>k)break; } } swap(cur,next); } int ans=0; rep(i,m+1)ans+=dp[cur][i][0]; printf("%d\n",ans); }
次が、dp[j][k]を求めるのに、一個前の値を再利用してオーダーを落とした方法(情報オリンピックの解説)
一個前の値を再利用ができるかは、DPの配列を表で書いて、依存関係を矢印で表したときに、だーっと集中してる矢印があるかを見てやるイメージな気がしてきた。
int n,m,s; int dp[2][2001][3001]; int main() { while(scanf("%d%d%d",&n,&m,&s),n){ rep(j,m+1)rep(k,s+1)dp[0][j][k]=0; dp[0][0][0]=1; m=min(m,s); int cur=0,next=1; for(int i=1;i<=n*n;i++){ rep(j,2)rep(k,s+1)dp[next][i-1+j][k]=0; for(int j=i-1;j<m;j++)for(int k=n*n-i+1;k<=s;k++){ dp[next][j+1][k]=dp[cur][j][k-(n*n-i+1)]+dp[next][j][k-(n*n-i+1)]; if(dp[next][j+1][k]>100000)dp[next][j+1][k]-=100000; } swap(cur,next); } int ans=0; rep(i,m+1)ans+=dp[cur][i][s]; printf("%d\n",ans%100000); } return 0; }
ちなみにmodをとる部分を%演算子を使うとTLEになる。
が、これでもMLEになってしまうので、テーブルの更新順序を工夫することで、テーブルを使いまわしてメモリを節約する。
3次元の表は2次元のテーブルのセルがさらに細かく部屋に分かれてるイメージでやると更新の関係とか掴みやすいかなあ。
あーうーほんっとDPが全然できないorzorzorzorzorz
ソースコード
int n,m,s; int dp[50][3001]; int main() { while(scanf("%d%d%d",&n,&m,&s),n){ rep(i,n*n+1)rep(j,s+1)dp[i][j]=0; dp[0][0]=1; m=min(m,s); rep(i,m){ for(int j=n*n;j>0;j--)for(int k=s;k>n*n-j;k--){ dp[j][k]=dp[j-1][k-(n*n-j+1)]+dp[j][k-(n*n-j+1)]; if(dp[j][k]>100000)dp[j][k]-=100000; } } printf("%d\n",dp[n*n][s]); } return 0; }