洛谷P4831 Scarlet loves WenHuaKe

题面

题意

给出一个 m ∗ n m*n mn的棋盘,问在其中放 2 ∗ m 2*m 2m个炮,使他们两两不会攻击的方案数是多少。

做法

当n,m都小于等于2000时,直接记录有几列放了一个棋子,几列没放棋子,即可逐行转移。
当大于2000时,可以利用 n − m < = 10 n-m<=10 nm<=10的性质,考虑最后一列放几个棋子,分以下三种方案进行讨论:
1.最后一列放两个棋子,考虑这两个棋子所在的两行的另外两颗棋子a,b:
如果a,b在同一列,则 f ( m , n ) + = f ( m − 2 , n − 2 ) ∗ ( m ∗ ( m − 1 ) / 2 ) ∗ ( n − 1 ) f(m,n)+=f(m-2,n-2)*(m*(m-1)/2)*(n-1) f(m,n)+=f(m2,n2)(m(m1)/2)(n1)
反之,则可以合并这两行 f ( m , n ) + = f ( m − 1 , n − 1 ) ∗ ( m ∗ ( m − 1 ) / 2 ) ∗ 2 f(m,n)+=f(m-1,n-1)*(m*(m-1)/2)*2 f(m,n)+=f(m1,n1)(m(m1)/2)2
2.最后一列只放一个棋子,则这个棋子所在行的另一个棋子的所在列最多放1个棋子,因此只要减去那一列放两个棋子的方案数即可直接转移。
3.最后一列不放棋子,则 f ( m , n ) + = f ( m , n − 1 ) f(m,n)+=f(m,n-1) f(m,n)+=f(m,n1)

代码

#include
#define ll long long
#define P pair
#define mp make_pair
#define fi first
#define se second
#define N 100100
#define M 998244353
using namespace std;

ll m,n,dp[N][15];

inline ll C2(ll u){return u*(u-1)/2%M;}
ll dfs(ll u,ll v);
inline ll calc(ll u,ll v){return C2(u)*((2*dfs(u-1,v)+(u+v-1)*dfs(u-2,v)%M)%M)%M;}
ll dfs(ll u,ll v)
{
    if(!u&&!v) return 1;
    if(u<0 || v<0) return 0;
    if(dp[u][v]!=-1) return dp[u][v];
    ll res=0;
    res+=calc(u,v);
    if(v)
    {
	res+=u*(u+v-1)%M*((dfs(u-1,v)+M-calc(u-1,v))%M)%M;
	res+=dfs(u,v-1);
    }
    return dp[u][v]=res%M;
}

namespace solve
{
    ll jl[2010][2010];
    ll dfs(ll u,ll v)
    {
	if(u*2+v+m*2==n*2) return 1;
	if(jl[u][v]!=-1) return jl[u][v];
	ll res=0;
	if(u>1) res+=dfs(u-2,v+2)*u*(u-1)/2%M;
	if(u&&v) res+=dfs(u-1,v)*u*v%M;
	if(v>1) res+=dfs(u,v-2)*v*(v-1)/2%M;
	return jl[u][v]=res%M;
    }
    void work()
    {
	memset(jl,-1,sizeof(jl));
	cout<<dfs(n,0);
    }
}

int main()
{
    memset(dp,-1,sizeof(dp));
    ll i,j;
    cin>>m>>n;
    if(m<=2000&&n<=2000)
    {
	solve::work();
	return 0;
    }
    cout<<dfs(m,n-m);
}

你可能感兴趣的:(经典,技巧,dp)