2020 ICPC, COMPFEST 12, Indonesia Multi-Provincial Contest B.Blue and Red of Our Faculty!(dp/背包)

题目

n(2<=n<=2e3)个点的图,m(m<=2*n)条无向边

除了1号点度数无限制以外,别的点恰有两条边与之相连,

无自环,可能有重边

红方和蓝方开始都在1号点,所有边都是未被涂色的,

每一次红蓝各选与当前所在节点相邻的一条未被涂色的边,

然后分别将其涂上对应颜色,并走到节点的另一端

如果有一刻二人不能同时选中两条边,游戏结束

问有多少种终局方案,答案对1e9+7取模

两种方案不同当且仅当至少有一条边在出现在planA里没出现在planB里

思路来源

wucstdio代码

题解

首先,这是一个类似花瓣的形状,花瓣中心是1,套着若干个圆环瓣,

先处理出来每个环长,然后考虑做背包,分四种情况

有两种是二人在某个环的中间相遇,分在点上相遇,

和在边上相遇(由于中间只有一条边,无法满足同时选中两条边的要求)

 另两种是,两人都在一号点,一人在一号点另一人与之隔一条边

 

dp[i][j][2]表示当前考虑前i个环里,有些环可以不走,红-蓝的边数=j,当前有无相遇的方案数,

第三维用于枚举最后相遇环时满足无后效性,

转移可以忽略这个环,让红走完这个环,让蓝走完这个环

 

tp[i][j][2]用于处理后两种情况,

tp[i][j][2]表示当前考虑前i个环,所有环都必须完整走完,红-蓝的边数=j时,当前有无相遇的方案数,

转移考虑让红走完,或者让蓝走完,

则最后无相遇是tp[c][0][0]表示在1号点相遇的方案,

而tp[c][0][1]是枚举了最后一个环的位置,这个位置距离1号点恰好一条边之遥

 

将三者求和即为最终答案

代码

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=2e3+10,M=4e3,O=2*M+5,mod=1e9+7;
int n,m,a[N],c,u,v;
vector<int>e[N];
bool vis[N];
int dp[N][O][2],tp[N][O][2],ans;
void add(int &x,int y){
	x+=y;
	if(x>=mod)x-=mod;
	if(x<0)x+=mod;
}
int dfs(int u){
	vis[u]=1;
	for(int v:e[u]){
		if(vis[v])continue;
		return dfs(v)+1;
	}
	return 1;
} 
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i){
		scanf("%d%d",&u,&v);
		e[u].pb(v);e[v].pb(u);
	}
	vis[1]=1;
	for(int v:e[1]){
		if(!vis[v]){
			a[++c]=dfs(v)+1;
		}
	}
	dp[0][M][0]=tp[0][M][0]=1;
	int sz=0;
	for(int i=1;i<=c;++i){
		for(int j=-sz;j<=sz;++j){
			add(dp[i][j+a[i]+M][0],dp[i-1][j+M][0]);//红 
			add(dp[i][j-a[i]+M][0],dp[i-1][j+M][0]);//蓝
			add(dp[i][j+M][0],dp[i-1][j+M][0]);//不 
			
			add(dp[i][j+a[i]+M][1],dp[i-1][j+M][1]);//红 
			add(dp[i][j-a[i]+M][1],dp[i-1][j+M][1]);//蓝
			add(dp[i][j+M][1],dp[i-1][j+M][1]);//不 
			
			add(tp[i][j+a[i]+M][0],tp[i-1][j+M][0]);//红 
			add(tp[i][j-a[i]+M][0],tp[i-1][j+M][0]);//蓝
			
			add(tp[i][j+a[i]+M][1],tp[i-1][j+M][1]);//红 
			add(tp[i][j-a[i]+M][1],tp[i-1][j+M][1]);//蓝
			
			for(int x=1;x<a[i];++x){//红x蓝y 
				int y=a[i]-x;
				add(dp[i][j+x-y+M][1],2ll*dp[i-1][j+M][0]%mod);//环内点相遇 顺逆 
			}
			for(int x=1;x<a[i]-1;++x){
				int y=(a[i]-1)-x;
				add(dp[i][j+x-y+M][1],2ll*dp[i-1][j+M][0]%mod);//环内边相遇 顺逆
			}
			add(tp[i][j+(a[i]-1)+M][1],2ll*tp[i-1][j+M][0]%mod);//红 1号点和边相遇 顺逆
			add(tp[i][j-(a[i]-1)+M][1],2ll*tp[i-1][j+M][0]%mod);//蓝 1号点和边相遇 顺逆
		}
		sz+=a[i];
	}
	add(ans,dp[c][M][1]);
	add(ans,tp[c][M][0]);
	add(ans,tp[c][M][1]);
	printf("%d\n",ans);
	return 0;	
} 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值