1. 程式人生 > >洛谷P2051 [AHOI2009]中國象棋

洛谷P2051 [AHOI2009]中國象棋

連結

題解

  很厲害的題目。
  首先這個問題就是在N*M的網格中放若干個棋子使得每一行每一列至多有2個棋子。
  考慮xjb暴力,30%的資料N,M6,那就每一列壓成3進位制數,f[i][j]表示做了前i列,每一行的狀態是j,0表示這行沒有棋子,1表示有1個棋子,2表示有2個棋子,這樣壓成3進位制然後dp轉移就可以略了。
  觀察最大的資料N,M100,顯然不可狀壓。容易發現我們只關心最後的方案數,而不關心棋子的具體分佈,因此狀態改為f[i][j][k],表示做了前i列,目前有j行是沒有棋子的,k行有1個棋子。
  轉移有點龐大,直說一個吧,當j>=1時,就是說有j個位置上是0,那麼下一行裡就可以在對應位置放一個棋子使得這個0變成1,所以轉移到f[i+1][j-1][k+1],而你要從j個0中選擇任意一個,所以f[i+1][j-1][k+1]+=f[i][j][k]*j,其餘的同理。具體程式碼中有註釋。

程式碼

//動態規劃+排列
#include <cstdio>
#include <algorithm>
#define maxn 110
#define mod 9999973ll
#define ll long long
using namespace std;
ll N, M, f[maxn][maxn][maxn], fact[maxn], a[maxn], b[maxn];

void exgcd(ll a, ll b, ll &x, ll &y)
{
    if(!b){x=1,y=0;return;}
    ll xx, yy;
    exgcd(b,a%b
,xx,yy); x=yy, y=xx-a/b*yy; } ll inv(ll a, ll p=mod) { ll x, y; exgcd(a,p,x,y); return (x+p)%p; } void init() { ll i; fact[0]=1; for(i=1;i<=100;i++)fact[i]=(fact[i-1]*i)%mod; for(i=0;i<=100;i++)a[i]=fact[i], b[i]=inv(fact[i]); } void dp() { ll i, j, k, t, ans=0; f[0
][N][0]=1; for(i=0;i<=M;i++) { for(j=0;j<=N;j++) { for(k=0;j+k<=N;k++) { t=f[i][j][k]%mod; f[i+1][j][k]+=t; if(j>=1)f[i+1][j-1][k+1]+=t*j; //0->1 if(j>=2)f[i+1][j-2][k+2]+=t*j*(j-1)/2;//0->1,0->1 if(k>=1)f[i+1][j][k-1]+=t*k; //1->2 if(k>=2)f[i+1][j][k-2]+=t*k*(k-1)/2; //1->2,1->2 if(j>=1 and k>=1)f[i+1][j-1][k]+=t*j*k; //0->1,1->2 } } } for(j=0;j<=N;j++)for(k=0;j+k<=N;k++)ans=(ans+f[M][j][k])%mod; printf("%lld",ans); } int main() { scanf("%lld%lld",&N,&M); init(); dp(); return 0; }