csp模拟 企鹅棋【计数dp】

传送门

每个点有向左,向右,都可以,三种跳跃属性。问从起点到终点的,经过每个点一次的方案数。

 

我们要求的就是合法的全排列,满足起点是第一个,终点是最后一个,任何一个可以到下一个的数量。

我们考虑最后的这个排列,从1开始,对每个数考虑其应该放在哪里。

我们发现,任何时刻,这样放出来的连通块,右端点一定是R或B,左端点一定是L或B。

因为我们从1~n添加,所以没有添加的数一定比添加了的数大。如果是右端点,就要到达一个比自己大的,那我只能向右。

反之就是向左。

 

然后我们发现,要产生这样的限制,只能说明我跟连通块发生了关系(或者自己成了一个新连通块)

所以可以设置状态f[i][j]表示考虑到数i,现在有j个连通块的方案数。

但是我们发现,起点和终点的位置是确定的,要提出来讨论。所以我们设free为不是起点,终点的连通块数量。

所以状态转移是这样的:

如果i+1是起点:

其要么自成一个新连通块,要么和一个普通连通块连在一起。

如果i+1是终点:

其要么自成一个新连通块,要么和一个前面的普通连在一起。

如果都不是:

1:自成一个连通块

2。和起点连通块连接。

3。和起点连通块以及一个普通连通块连接。

4。和终点连接。

5。和终点连通块以及一个普通连通块连接。

6。成为一个普通块的右端点。

7。成为一个普通块的左端点。

8。连接两个普通联通块。

 

然后就写吧,,

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
const int mod=1e9+7;
int f[5003][5003];
char ch[5003],n,s,e;
signed main(){
	scanf("%s",ch+1);
	n=strlen(ch+1);s=in;e=in;
	f[0][0]=1;
	for(int i=0;i<=n-2;i++){
		int st;if(i==0)st=0;else st=1;
		for(int j=st;j<=i;j++){
			long long key=f[i][j];if(!key)continue;
			int free=j;
			if(i>=s)free--;if(i>=e)free--;if(free<0)continue;char x=ch[i+1];
			if(i+1==s){
				if(x=='R'||x=='B')f[i+1][j+1]=(f[i+1][j+1]+key)%mod;
				if(x=='L'||x=='B')f[i+1][j]=(f[i+1][j]+key*free)%mod;
			}
			else if(i+1==e){
				f[i+1][j+1]=(f[i+1][j+1]+key)%mod;
				f[i+1][j]=(f[i+1][j]+key*free)%mod;
			}
			else{
				if(x=='R'||x=='B')f[i+1][j+1]=(f[i+1][j+1]+key)%mod;
				if(i+1>s&&(x=='R'||x=='B'))f[i+1][j]=(f[i+1][j]+key)%mod;
				if(i+1>s&&(x=='L'||x=='B')&&j)f[i+1][j-1]=(f[i+1][j-1]+key*free)%mod;
				if(i+1>e&&(x=='L'||x=='B'))f[i+1][j]=(f[i+1][j]+key)%mod;
				if(i+1>e&&(x=='L'||x=='B')&&j)f[i+1][j-1]=(f[i+1][j-1]+key*free)%mod;
				if(x=='R'||x=='B')f[i+1][j]=(f[i+1][j]+free*key)%mod;
				if(x=='L'||x=='B')f[i+1][j]=(f[i+1][j]+free*key)%mod;
				if(free>=2&&(x=='L'||x=='B'))f[i+1][j-1]=(f[i+1][j-1]+key*free%mod*(free-1)%mod)%mod;
			}
		}
	}int ans=0;
	if(n==s){if(ch[n]=='L'||ch[n]=='B')ans=f[n-1][1];}
	else if(n==e)ans=f[n-1][1];
	else if(ch[n]=='L'||ch[n]=='B')ans=f[n-1][2];
	cout<<ans;
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值