4-4模拟赛 囚人的旋律——DP

前置技能点:DP,上升子序列

如果你不知道上面的东西,请先行了解

start_of_题面

【问题描述】

被诅咒的监狱里流淌着囚人们的歌谣。
将罪恶的青春全部抹杀殆尽。
“看守”执掌“囚犯”的生杀大权。
“囚犯”中藏着可以杀掉“看守”的恶魔。
这就是,将人性扭曲的,“监狱游戏”。

监狱游戏的参加者被分为了看守和囚犯,两侧各 n n n人。举行监狱游戏的地点在一个被改造过了的大仓库一样的地方,里面有两排共 2 n 2n 2n个房间,服务入口侧的是囚犯的房间,行刑室侧的是看守的房间。如下图所示

4-4模拟赛 囚人的旋律——DP_第1张图片

相邻的看守与囚犯的房间之间可以通过对讲机互相沟通,但是声音会被处理,无法辨别。两侧的分类房中都有一排各n扇门,从左到右编号为 1 ∼ n 1 ∼ n 1n。进入一扇门之后会有一条狭长、黑暗,而且弯弯曲曲的走廊通向房间。由于其特殊的构造,看守的i号门对应房间未必就是囚犯的i号门对应的房间。因此,想在这个监狱游戏中胜出,了解门与门之间的对应关系是很有必要的。

接下来的问题就和监狱游戏没有太多关系了。我们令 a [ i ] a[i] a[i]表示看守的第i扇门对应囚犯的哪一扇门。令图G为有n个节点的图,编号为 1 ∼ n 1 ∼ n 1n。对于满足 1 ≤ i < j ≤ n 1 ≤ i <j ≤ n 1i<jn一对 i i i j j j,如果有 a [ i ] > a [ j ] a[i] > a[j] a[i]>a[j],那么在 G G G中编号为 i i i j j j的节点之间连一条边。得到的图 G G G被称为逆序图。

对于图 G = ( V , E ) G = (V, E) G=(V,E),非空点集 S ∈ V S ∈ V SV是一个独立集当且仅当对于任意两个点 u , v ∈ V u, v ∈ V u,vV,不存在 u , v ∈ E u, v ∈ E u,vE。而 S S S是一个覆盖集当且仅当对于任意点 v ∉ S v ∉ S v/S存在点 u ∈ S u ∈ S uS满足 u , v ∈ E u, v ∈ E u,vE

我们在意的是,图 G G G中有多少个点集既是独立集又是覆盖集。出于某种不知
名的原因,被迫参加监狱游戏的大家的安危和这个问题的答案有关。拜托了,请
一定要求出这个方案数。

【输入格式】

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

【输出格式】

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

【样例输入】

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

【样例输出】

3

【样例解释】

样例中的逆序图如下图所示:
4-4模拟赛 囚人的旋律——DP_第2张图片

序列 2 , 4 , 5 , 1 , 3 {2,4,5,1,3} 2,4,5,1,3的逆序图即为此图。满足条件的点集有{ 1 , 2 , 3 1,2,3 1,2,3}、{ 1 , 5 1,5 1,5}和{ 4 , 5 4,5 4,5}。
可以证明不存在其他满足条件的点集。

【数据规模和约定】
对于 20%的数据, n ≤ 20 n ≤ 20 n20
对于 60%的数据, n ≤ 100 n ≤ 100 n100
对于 100%的数据, n ≤ 1000 n ≤ 1000 n1000 0 ≤ m ≤ n ( n − 1 ) 2 0 ≤ m ≤ \frac{n(n-1)}{2} 0m2n(n1)

end_of_题面

先说一句,题面有毒

然后看题,发现之前做过一道类似的,那道题的图也是一样的规则,问题是求最大独立集,显然这样就是直接一个最长上升子序列就可以了。

回到这道题,首先要求是一个最大独立集,所以肯定是一个上升序列,有要求是一个覆盖集,也就是说每一个没有选的点,必须至少和一个选了的点连边,所以这个上升序列的最后一个的右侧不能有比它大的元素,这样是不合法的,所以我们要求的是极长上升子序列

我们可以以 f [ i ] f[i] f[i]表示以 i i i结尾的方案数,如果它可以转移到 f [ j ] f[j] f[j],则需要满足两个条件,一个是 a [ j ] > a [ i ] a[j]>a[i] a[j]>a[i],另一个是 m i n { a [ k ] } > a [ j ] ( i < k < j 且 a [ k ] > a [ i ] ) min \{a[k]\} > a[j](i < k < j 且a[k]>a[i]) min{a[k]}>a[j](i<k<ja[k]>a[i]),但是这样不太好统计答案,所以我们加一位 a [ n + 1 ] = n + 1 a[n+1]=n+1 a[n+1]=n+1,并且规定必须选它,这样答案就变成了 f [ n + 1 ] f[n+1] f[n+1]

对于把图转换成序列,可以直接用拓扑排序

start_of_code

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

#define LL long long

const int mod = 1000000007;

LL n, m, ans = 0;
LL In[1010], a[1010], f[1010];
struct cmp{
	bool operator()(int a, int b)
	{
		return a < b;
	}
};
priority_queue<int, vector<int>, cmp> q;
vector<int> e[1010];

namespace file{
	inline void open()
	{
		freopen("senritsu.in", "r", stdin);
		freopen("senritsu.out", "w", stdout);
	}

	inline void close()
	{
		fclose(stdin);
		fclose(stdout);
	}
}

namespace input{
	inline LL read()
	{
		LL a = 0;
		LL f = 1;
		char ch;
		while(!((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-')));
		if(ch == '-')
			f = -1;
		else
		{
			a = a * 10;
			a += ch - '0';
		}
		while((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-'))
		{
			a = a * 10;
			a += ch - '0';
		} 
		return a * f;
	}
}

int main()
{
	file::open();
	n = input::read(), m = input::read();
	for(int i = 1;i <= m;++i)
	{
		int x = input::read(), y = input::read();
		if(x > y)
			swap(x, y);
		e[x].push_back(y);
		++In[y];
	}
	for(int i = 1;i <= n;++i)
		if(In[i] == 0)
			q.push(i);
	int Top = n;
	while(!q.empty())
	{
		int f = q.top();q.pop();
		a[f] = Top;
		--Top;
		int s = e[f].size();
		for(int i = 0;i < s;++i)
		{
			int v = e[f][i];
			--In[v];
			if(!In[v])
				q.push(v);
		}
	}
	a[0] = 0, a[n + 1] = n + 1;
	f[0] = 1;
	for(int i = 0;i <= n;++i)
	{
		int r = n + 2;
		for(int j = i + 1;j <= n + 1;++j)
		{
			if(a[j] < a[i] || a[j] >= r)
				continue;
			f[j] += f[i];
			if(f[j] >= mod)
				f[j] -= mod;
			r = a[j];
		}
	}
	printf("%lld\n", f[n + 1] % mod);
	file::close();
	return 0;
}

end_of_code

你可能感兴趣的:(DP)