HDU5079 Square
【题目大意】
给你一个
n×n(n≤8)
的棋盘,上面有一些格子必须是黑色,其它可以染黑或者染白,对于一个棋盘,定义它的优美度为它上面最大的连续白色子正方形的边长,对于每个0≤i≤n,问有多少种染色方案使得棋盘的优美度为i?
【解题思路】
比较显然的是这个题可以拆成n+1问来处理,每一问相当于求出优美度为i的方案数,但如果我们直接求恰好为i的方案数是比较困难的。考虑转化一下问题,我们就可以求出优美度至多为i的有多少种,这样答案显然就是求个差了。以下考虑我们现在要求的i为sz。
然后n最大才8,一眼看过去满满的状压味道,于是我们开始考虑状压。
一般的状压都是记这一个位置上有没有,但是这里不一样,这里对于解题有一个关键点——长度。然而长度最大也才8,我们可以考虑用一个数字将它记下。
我们发现,对于每一个格子,影响关于这个格子的最大正方形可以包括他的上方和包括它的右侧(有点乱,意思一下)。我们可以用
f[i][sta]
来表示DP到了第i行,该行状态为sta的方案数。 这里的sta显然不止是用01来表示的,我们可以把它看成一个n进制,这个sta的每一位代表
i
到
这样问题变迎刃而解,因为我们发现它很容易转移。。。。。。
很显然DP方程为
f[i][newsta]=∑f[i−1][sta]
,newsta为合法的情况(即去掉超过当前要求的边长的)。
然后乱搞就好了。
【参考代码】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=10;
const int MAXM=1e5;
const int mod=1e9+7;
int n,m,T;
int f[MAXN][MAXM];
int cnb[MAXN],pws[MAXN][MAXN],ans[MAXN];//cnb=the state can not be
int flag[MAXN];//in or not
char s[MAXN];
inline void get_pows()
{
for(int i=2;i<MAXN-1;++i)
{
pws[i][0]=1;
for(int j=1;j<MAXN-1;++j)
pws[i][j]=pws[i][j-1]*i;
}
}
inline void _reset()
{
m=1;
// memset(f,0,sizeof(f));
memset(ans,0,sizeof(ans));
memset(cnb,0,sizeof(cnb));
}
inline void init()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%s",s);
for(int j=0;j<n;++j)
{
if(s[j]=='o')
m=(m<<1)%mod;
else
cnb[i]|=(1<<j);
}
}
}
inline void solve()
{
ans[0]=1;ans[1]=(m-1+mod)%mod;
for(int sz=2;sz<=n;++sz)//size
{
int k=n-sz+1,ms=pws[sz][k];
// printf("ms:%d\n",ms);
f[0][0]=1;
for(int sta=1;sta<ms;++sta)
f[0][sta]=0;
for(int i=1;i<=n;++i)
{
for(int sta=0;sta<ms;++sta)
f[i][sta]=0;
for(int cur=0;cur<(1<<n);++cur)
{
if(cur&cnb[i])
continue;
// printf("%d %d here\n",cur,cnb[i]);
memset(flag,0,sizeof(flag));
for(int j=0;j<n;++j)//line i,row j
{
if(cur&(1<<j))
continue;
for(int q=0;q<k;++q)
if(j>=q && j<q+sz)
flag[q]=1;
}
// for(int q=0;q<k;++q)
// printf("%d ",flag[q]);
// printf("\n");
for(int sta=0;sta<ms;++sta)
{
if(!f[i-1][sta])
continue;
int nsta=0;
// printf("%d\n",i);
// printf("here\n");
for(int j=0;j<k;++j)
{
int t=sta/pws[sz][j]%sz;//sz jingzhi,the jth num
// printf("t:%d\n",t);
if(flag[j])
t=0;
else
if(t!=sz-1)
++t;
else
{
nsta=-1;
break;
}
nsta+=t*pws[sz][j];
// printf("new:%d\n",nsta);
}
if(nsta==-1)
continue;
f[i][nsta]=(f[i][nsta]+f[i-1][sta])%mod;
}
}
}
// printf("%d:\n",sz);
// for(int i=0;i<ms;++i)
// printf("%d\n",f[sz][i]);
// printf("\n\n");
for(int sta=0;sta<ms;++sta)
ans[sz]=(ans[sz]+f[n][sta])%mod;
ans[sz]=(m-ans[sz]+mod)%mod;
ans[sz-1]=(ans[sz-1]-ans[sz]+mod)%mod;
}
for(int sz=0;sz<=n;++sz)
printf("%d\n",ans[sz]);
}
int main()
{
freopen("HDU5079.in","r",stdin);
freopen("HDU5079.out","w",stdout);
get_pows();
scanf("%d",&T);
while(T--)
{
_reset();
init();
solve();
}
return 0;
}
LGP2051 [AHOI2009]中国象棋
【题目描述】
这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!
【输入输出格式】
【输入格式】
一行包含两个整数N,M,之间由一个空格隔开。
【输出格式】
总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。
【输入样例】
1 3
【输出样例】
7
【数据范围】
100%的数据中N和M均不超过100
50%的数据中N和M至少有一个数不超过8
30%的数据中N和M均不超过6
【题目分析】
说实话,这题在LG上是紫色,但个人认为并没有到这个程度,因为太好想了。。。
我们发现,若我们对行进行dp,则做到当前行的方案数,只和之前有多少列放了多少个棋子有关,和具体怎么放实际上是无关的。显然我们可以考虑:
令
f[i][j][k]
为做到第i行,一共有j列放了1个棋子,有k列放了2个棋子的方案数。
然后胡乱转移就行了。
【参考代码】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int mod=9999973;
const int MAXN=105;
int ans,n,m;
LL f[MAXN][MAXN][MAXN];//f[i][j][k]=now row i,have j rows 1chess,have k rows 2chess
int main()
{
freopen("LGP2051.in","r",stdin);
freopen("LGP2051.out","w",stdout);
scanf("%d%d",&n,&m);
f[1][0][0]=1;f[1][1][0]=m;f[1][2][0]=m*(m-1)/2;
for(int i=2;i<=n;++i)
{
for(int j=0;j<=m;++j)
{
for(int k=0;k<=m;++k)
{
LL tot=0;
tot+=f[i-1][j][k];//nothing
tot+=f[i-1][j+1][k-1]*(j+1);//put 1 in 1
tot+=f[i-1][j-1][k]*(m-j-k+1);//put 1 in 0
tot+=f[i-1][j-2][k]*(m-j-k+2)*(m-j-k+1)/2;//put 2 in 0
tot+=f[i-1][j+2][k-2]*(j+2)*(j+1)/2;//put 2 in 1
tot+=f[i-1][j][k-1]*j*(m-j-k+1);//put 1 in 0,1 in 1
f[i][j][k]=tot%mod;
}
}
}
for(int i=0;i<=m;++i)
for(int j=0;j<=m;++j)
ans=(ans+f[n][i][j])%mod;
printf("%d\n",ans);
return 0;
}