BZOJ3444: 最后的晚餐

3 篇文章 0 订阅
3 篇文章 0 订阅

【问题背景】
高三的学长们就要离开学校,各奔东西了。某班n人在举行最后的离别晚餐时,饭店老板觉得十分纠结。因为有m名学生偷偷找他,要求和自己暗恋的同学坐在一起。
【问题描述】
饭店给这些同学提供了一个很长的桌子,除了两头的同学,每一个同学都与两个同学相邻(即坐成一排)。给出所有信息,满足所有人的要求,求安排的方案总数(这个数字可能很大,请输出方案总数取余989381的值,也可能为0)。

这问题背景真真真不煽情 啊。咳咳,走远了。


其实坐不坐在一起是无向的,我挨着你坐,那你肯定也挨着我坐。所以建一个无向森林,把所有要挨到一起的东西连上,然后发现这个森林里面的每一个联通块其实都应该是一条链。否则肯定无解。把所有链找出来之后打包,每一条链是有两种方法的顺这和倒着,若有a个人没有要求,一共有b条链那答案就是
那现在就来考虑怎么找链,好像可以直接用并查集维护。我比较沙茶,写的拓扑(虽然是无向图,但我建立的是双向有向边,拓扑每次考虑入度≤1的去搞)。
先考虑有两种情况是无解的,第一种就是链分叉了,第二种就是成环了。
第一种直接看如果连了A,B一条新边,看A,B的入度若≥3,那肯定就分叉了。直接0.
第二种就在拓扑中记录已经访问了多少点,若和有要求的人数不同,那么肯定就有环了。
然后这个水题就完了。
这种写法有个trick,就是如果A暗恋B,B暗恋A,这相当于有重边了,我每次都建立两个单向边,这下就建立了4个单向边,就搞成环了。
哎这么美妙的事情,为什么在程序中是个TRICK!哎,不是每件好事的发生对于人类的进展都是有益的。唉唉唉。。为什么我非要破坏了这美好一刻。
说点题外话:每个人都只会暗恋一个,所以只用开一个数组记一下,虽然我暴力map纯二啊!(这为什么是题外话。。)
最后:希望大家都有一个双向边,但一定不要出现多叉。

#include <cstdio>
#include <algorithm>
#include <map>
#define rep(i,l,r) for (int i=l;i<=r;++i)
typedef long long LL;
int getx(){
	char c;int x;
	for (c=getchar();c<'0'||c>'9';c=getchar());
	for (x=0;c>='0'&&c<='9';c=getchar())
		x=(x<<3)+(x<<1)+c-'0';
	return x;
}
const LL MOD=989381;
const int MAX_N=500005;
int first[MAX_N],next[MAX_N<<1],to[MAX_N<<1];
int rd[MAX_N];
int tal=0;
void tjb(int x,int y){
	next[++tal]=first[x];
	first[x]=tal;
	to[tal]=y;
	rd[y]++;
}
int tot=0;
bool vis[MAX_N];
void topo(int v,int fa){
	vis[v]=true;tot--;
	for (int k=first[v];k;k=next[k]){
		int u=to[k];
		if (u==fa) continue;
		if (--rd[u]<=1) topo(u,v);
		}
}
LL power(LL a,LL b){
	LL res=1;
	for (;b;a=((LL)a*a)%MOD,b>>=1)
		if (b&1) res=((LL)res*a)%MOD;
	return res;
}
int n,m;
std::map<int,bool> edge[MAX_N];
bool choose[MAX_N];
int main(){
	n=getx(),m=getx();
	rep(i,1,m){
		int x=getx(),y=getx();
		if (x>y) x^=y,y^=x,x^=y;
		if (!choose[x]){choose[x]=true;tot++;}
		if (!choose[y]){choose[y]=true;tot++;}
		if (!edge[x][y]){
			tjb(x,y),tjb(y,x);
			if (rd[x]>2||rd[y]>2) {printf("0");exit(0);}
			edge[x][y]=true;
			}
		}
	int s=n-tot;
	int k=0;
	rep(i,1,n)
		if (!vis[i]&&rd[i]==1){k++;topo(i,0);}
	if (tot>0){printf("0");exit(0);}
	LL ans=1;
	rep(i,1,s+k) ans=ans*i%MOD;
	printf("%d\n",ans*power(2,k)%MOD);
}



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值