2021-06-04

写一道记一道(真是太难了)

P2051 [AHOI2009]中国象棋

在一个 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是两个数
大力分类转换即可

顾Z学长的题解

P4026 [SHOI2008]循环的债务

他们决定要在清还债务的时候尽可能少的交换现金。

比如说,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);
}

这就是状压了。用一个二进制运算来搞是否矛盾,减少空间,

P3146 [USACO16OPEN]248 G

#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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值