写一道记一道(真是太难了)
在一个 n 行 m 列的棋盘上,让你放若干个炮(可以是 0 个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。(n,m<=100).
#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
int dp[103][103][103];
const int ch=9999973;
int n,m;
#define f dp
int Combination(int x)
{
return ((x*(x-1))>>1);
}
void I_am_at_the_station_level()
{
int maxn=0;
dp[0][0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k<=m-j;k++)
{
dp[i][j][k]+=dp[i-1][j][k];
if(k>=1)
f[i][j][k]+=f[i-1][j+1][k-1]*(j+1)%ch;
if(j>=1)
f[i][j][k]+=f[i-1][j-1][k]*(m-j-k+1)%ch;
if(k>=2)
f[i][j][k]+=f[i-1][j+2][k-2]*Combination(j+2)%ch;
if(k>=1)
f[i][j][k]+=f[i-1][j][k-1]*j*(m-j-k+1)%ch;
if(j>=2)
f[i][j][k]+=f[i-1][j-2][k]*Combination(m-j-k+2)%ch;
dp[i][j][k]%=ch;
}
for(int i=0;i<=m;i++)
for(int j=0;j<=m;j++)
{
maxn+=dp[n][i][j];
maxn%=ch;
}
printf("%d",maxn);
}
signed main()
{
cin>>n>>m;
I_am_at_the_station_level();
}
一开始以为是状压…
一行一列最多2个棋子,i是行数,j一个数,k是两个数
大力分类转换即可
他们决定要在清还债务的时候尽可能少的交换现金。
比如说,Alice欠Bob 10元,而Cynthia和他俩互不相欠。现在假设Alice只有一张50元,Bob有3张10元和10张1元,Cynthia有3张20元。一种比较直接的做法是:Alice将50元交给Bob,而Bob将他身上的钱找给Alice,这样一共就会有14张钞票被交换。但这不是最好的做法,最好的做法是:Alice把50块给Cynthia,Cynthia再把两张20给Alice,另一张20给Bob,而Bob把一张10块给Cynthia,此时只有5张钞票被交换过。
他们发现问题,于是他们找到你为他们解决这个难题。
P1896 [SCOI2005]互不侵犯
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
int n,kk,num;
int cnt[2000],pd[2000];
int dp[10][100][2000];
signed main()
{
cin>>n>>kk;
for(int i=0;i<(1<<n);i++)
{
int tot=0,k=i;
while(k)
{
if(k&1)
tot++;
k>>=1;
}
cnt[i]=tot;
if((((i<<1)|(i>>1))&i)==0) //左右
pd[++num]=i;
}
dp[0][0][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=num;j++)
{
int o=pd[j];
for(int k=1;k<=num;k++)
{
int oo=pd[k];
if(((oo|(oo<<1)|(oo>>1))&o)==0)//上下,左下右上,左上右下方向都不相邻
for(int p=0;p<=kk;p++)
if(p-cnt[o]>=0)
dp[i][p][o]+=dp[i-1][p-cnt[o]][oo];
}
}
int ans=0;
for(int i=1;i<=num;i++)
ans+=dp[n][kk][pd[i]];
printf("%lld",ans);
}
这就是状压了。用一个二进制运算来搞是否矛盾,减少空间,
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int dp[250][250];
int n;
int main()
{
int ans=-999999;
cin>>
n;
memset(dp,0xcf,sizeof(dp));
for(int i=1;i<=n;++i)
{
cin>>
dp[i][i];
}
for(int i=2;i<=n;i++)
for(int l=1;l<=n-i+1;l++)
{
int
r=l+i-1;
for(int k=l;k<r;k++)
if(dp[l][k]==dp[k+1][r])
{
dp[l][r]=
max(dp[l][r],dp[l][k]+1);
ans=
max(ans,dp[l][r]);
}
}
cout<<
ans;
}
大水区间DP,枚举长度,左端点(从而有了右端点),在枚举断点OK。