【BZOJ4715】囚人的旋律 DP

【BZOJ4715】囚人的旋律

Description

我们令a[i]表示看守的第i扇门对应囚犯的哪一扇门。令图G为有n个节点的图,编号为1~n。对于满足1≤i<j≤n的一对i和j,如果有a[i]>a[j],那么在G中编号为i和j的节点之间连一条边。得到的图G被称为逆序图。对于图G=(V,E),非空点集S∈V是一个独立集当且仅当对于任意两个点u,v∈V,不存在(u,v)∈E。而S是一个覆盖集当且仅当对于任意点v?S存在点u∈S满足(u,v)∈E。我们在意的是,图G中有多少个点集既是独立集又是覆盖集。出于某种不知名的原因,被迫参加监狱游戏的大家的安危和这个问题的答案有关。拜托了,请一定要求出这个方案数。

Input

输入第一行含有两个整数n和m,表示逆序图的点数和边数。
接下来m行,每行描述一条边。每行包含两个1~n的整数,代表边的两个端点。保证没有重边和自环。
保证给定的图是一个合法的逆序图,即,存在至少一个序列,按照题目描述中所述方法得到的逆序图是给定的图。
n≤1000,0≤m≤(n(n-1))/2

Output

输出一个整数,表示方案数对1,000,000,007取模得到的结果。

Sample Input

5 5
2 4
2 5
1 4
3 4
3 5

Sample Output

3

题解:首先,根据这个逆序图是可以在O(n^2)时间内将原序列还原出来的。

方法:统计每个数前面有多少个大于它的数pre,后面有多少个小于它的数nxt,那么最小的数i一定满足pre[i]=i-1&&nxt[i]=0,所以找出最小的数,然后将它删掉,更新其它点的pre和nxt。不断重复此过程即可。

那么本题求的东西到底是什么呢?一开始以为求的是边覆盖集,然后就变成了将原序列分成两个不相交的上升子序列,但是样例过不去。。。后来发现求的是点覆盖集。。。

因为选出来的序列是独立集,所以这个序列是递增的;又因为这是覆盖集,所以每个不在序列中的点都与某个序列中的点形成逆序对。所以我们求的就是极长上升序列的方案数!用f[i]表示前i个数中极长上升序列方案数,如果i是一个合法的结尾,则用f[i]更新答案即可。

 

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int P=1000000007;
int n,m,ans;
int map[1010][1010],f[1010],nxt[1010],pre[1010],v[1010],bef[1010],aft[1010];
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<'0'||gc>'9')	{if(gc=='-')	f=-f;	gc=getchar();}
	while(gc>='0'&&gc<='9')	ret=ret*10+gc-'0',gc=getchar();
	return ret*f;
}
int main()
{
	n=rd(),m=rd();
	int i,j,k,a,b;
	for(i=1;i<=m;i++)
	{
		a=rd(),b=rd();
		if(a>b)	swap(a,b);
		map[a][b]=1,nxt[a]++,pre[b]++;
	}
	for(i=1;i<=n;i++)	for(j=i+1;j<=n;j++)
	{
		if(!map[i][j])	bef[j]=aft[i]=1;
	}
	for(i=1;i<=n;i++)
	{
		for(k=1;k<=n;k++)	if(!v[k]&&!nxt[k]&&pre[k]==k-1)	break;
		v[k]=i;
		for(j=1;j<k;j++)	if(map[j][k])	nxt[j]--;
		for(j=k+1;j<=n;j++)	pre[j]++;
	}
	for(i=1;i<=n;i++)
	{
		f[i]=(f[i]+(!bef[i]))%P;
		for(k=n+1,j=i+1;j<=n;j++)	if(!map[i][j]&&v[j]<k)	k=v[j],f[j]=(f[j]+f[i])%P;
		if(!aft[i])	ans=(ans+f[i])%P;
	}
	printf("%d",ans);
	return 0;
}

 

posted @ 2017-10-07 10:33  CQzhangyu  阅读(482)  评论(0编辑  收藏  举报