题目大意:
给出一个n*m的棋盘,让你在格点上放棋子,每一行每一列最多放2个棋子,放棋子的总数数不限(可以为0),求方案数模9999973的结果。
100%的数据中N和M均不超过100
50%的数据中N和M至少有一个数不超过8
30%的数据中N和M均不超过6
30%做法:
6*6的棋盘上,一共最多放12个棋子,暴力搜素加点剪枝即可。
50%做法:
我们意识到有一维最多为8,这意味着什么呢?我们可以对这一维进行状压,设一个3进制的8位数,第i位上的数表示这一维的第i个位置上已经放了多少个棋子。假设行不超过8,设f【i】【j】表示dp到第i列,行的状况是3进制数中的j,然后考虑用上一行的值进行转移,在这一行不放棋子有1种,放一个棋子有8种,放2个棋子有28种,每一行一共37种可能合法方案。时间复杂度:
对于50%的数据来说时间复杂度最多2400万左右,怎么都不会超。
100%做法:
在50%做法的基础上我们继续思考,发现每一行每一列能放的棋子数量极少,最多只能放两个,也就是说当我们考虑一行一行的放棋子的时候,每一列只会有3种状态,还没在这列上放过棋子,放了1个棋子以及放了2个棋子。那么,我们转移的时候真的有必要记录下每一列的状态吗?答案是否定的,我们只需要知道这3种状态每一种状态的列的数量,然后对每一种状态进行整体考虑就可以了。那么,dp的思路就出来了,设f【i】【j】【k】为考虑到第i行,这前i行中有j列还没有放过棋子,有k列放了1个棋子,那么用列的总数减去(j+k)就可以知道有多少列放了2个棋子了,接下来考虑f【i】【j】【k】会对那些值产生贡献:
1.下一行一个棋子都不放
2.下一行在一个原本没有棋子的列放了1个棋子
3.下一行在一个原本有1个棋子的列放了1个棋子
4.在2个原本没有棋子的列各放一个棋子
5.在2个原本有1个棋子的列各放一个棋子
6.分别在1个原本没棋子的列和原本有一个棋子的列各放一个棋子
推荐番:《约会大作战》
处理好边界问题,这道题就大功告成了!
100分代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
long long n,m,ans,f[105][205][205];
int main()
{
cin>>n>>m;
f[1][m][0]=1;f[1][m-1][1]=m;if (m>=2)f[1][m-2][2]=m*(m-1)/2;
for (int i=2;i<=n;i++)
{
for (int j=0;j<=m;j++)
{
for (int k=0;k<=m-j;k++)
{
f[i][j][k]+=f[i-1][j][k];
if (k>=1)f[i][j][k]=(f[i][j][k]+f[i-1][j+1][k-1]*(j+1))%9999973;
f[i][j][k]=(f[i][j][k]+f[i-1][j][k+1]*(k+1))%9999973;
if (k>=2)f[i][j][k]=(f[i][j][k]+f[i-1][j+2][k-2]*(j+2)*(j+1)/2)%9999973;
f[i][j][k]=(f[i][j][k]+f[i-1][j][k+2]*(k+2)*(k+1)/2)%9999973;
f[i][j][k]=(f[i][j][k]+f[i-1][j+1][k]*(j+1)*k)%9999973;
}
}
}
ans=0;
for (int i=0;i<=m;i++)
{
for (int j=0;j<=m;j++)
ans=(ans+f[n][i][j])%9999973;
}
cout<<ans;
}