洛谷P4831 Scarlet loves WenHuaKe
阿新 • • 發佈:2019-01-01
這道題告訴我們推式子的時候頭要夠鐵。
題意
問一個\(n\times m\)的棋盤,擺上\(n\times 2\)箇中國象棋的炮使其兩兩不能攻擊的方案數,對\(998244353\)取模。
\((n\leq m\leq 2000)或(n\leq m\leq 100000且m-n\leq 10)\)。
題解
怎麼兩個資料範圍搞搞。
顯然合法方案等價於每行每列炮的數量不超過\(2\),那麼每一行就必定放\(2\)個炮了。
我們記\(f(n,m)\)為答案,考慮如何規約到規模更小的問題。
那麼我們列舉最後一行炮的個數,分三類情況:
\(1\)、個數為\(0\),規約到\(f(n,m-1)\)。
\(2\)
如果相同,則列舉這是哪一個\((\times (m-1))\),規約到\(f(n-2,m-2)\)。
如果不同,則這兩行可以合併(同一行的唯一要求就是兩個列不同),只要根據有序性\(\times 2\)即可,於是規約到\(f(n-1,m-1)\)。
\(3\)、個數為\(1\),那麼先列舉佔了最後一列的是哪一行\((\times n)\),再列舉這一行的另一個在哪一列\((\times (m-1))\),問題就轉化為\(n-1\)
那麼考慮容斥,用總方案數減去這一列放了兩個的方案數。前者就是\(f(n-1,m-1)\),對於後者,進行與情況\(2\)相似的討論,也可以進行計算。
可以發現\(n>m\)時\(f(n,m)=0\),於是複雜度就是\(O((m-n)n)\)。
程式碼裡為了方便我將\(m\)減去了\(n\)。
#include<cstdio> #include<cstring> const int mod=998244353,inv2=(mod+1)/2; inline int add(int a,int b) { return (a+=b)>=mod?a-mod:a; } inline int sub(int a,int b) { return (a-=b)<0?a+mod:a; } inline int mul(int a,int b) { return (long long)a*b%mod; } inline int qpow(int a,int b) { int res=1; for(;b;a=mul(a,a),b>>=1) if(b&1) res=mul(res,a); return res; } int n,m; namespace solver1 { const int N=2005; int memo[N][N]; inline void init() { memset(memo,-1,sizeof(memo)); memo[1][0]=0; memo[2][0]=1; memo[3][0]=6; return; } int f(int n,int m) { if(m<0) return 0; if(n==0) return 1; if(~memo[n][m]) return memo[n][m]; int res=0; //0 res=add(res,f(n,m-1)); //1 res=add(res,mul(mul(n,n+m-1),f(n-1,m))); if(n>=3) res=sub(res,mul(mul(n,n+m-1),mul(mul(mul(n-1,n-2),inv2),add(mul(n+m-2,f(n-3,m)),mul(2,f(n-2,m)))))); //2 if(n>=2) res=add(res,mul(mul(mul(n,n-1),inv2),add(mul(n+m-1,f(n-2,m)),mul(2,f(n-1,m))))); return memo[n][m]=res; } inline void main() { init(); printf("%d\n",f(n,m-n)); return; } } namespace solver2 { const int N=1e5+5; int memo[N][15]; inline void init() { memset(memo,-1,sizeof(memo)); memo[1][0]=0; memo[2][0]=1; memo[3][0]=6; return; } int f(int n,int m) { if(m<0) return 0; if(n==0) return 1; if(~memo[n][m]) return memo[n][m]; int res=0; //0 res=add(res,f(n,m-1)); //1 res=add(res,mul(mul(n,n+m-1),f(n-1,m))); if(n>=3) res=sub(res,mul(mul(n,n+m-1),mul(mul(mul(n-1,n-2),inv2),add(mul(n+m-2,f(n-3,m)),mul(2,f(n-2,m)))))); //2 if(n>=2) res=add(res,mul(mul(mul(n,n-1),inv2),add(mul(n+m-1,f(n-2,m)),mul(2,f(n-1,m))))); return memo[n][m]=res; } inline void main() { init(); printf("%d\n",f(n,m-n)); return; } } signed main() { scanf("%d%d",&n,&m); if(n<=2000&&m<=2000) solver1::main(); else solver2::main(); return 0; }