bzoj4715 囚人的旋律
4715: 囚人的旋律
Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 74 Solved: 48
[Submit][Status][Discuss]
Description
「不知从何处,流淌出令人熟悉的旋律。
我到底是在哪里,听过这个旋律?」
——「『囚人的旋律』,是加入了诅咒的旋律哦」
【问题描述】
被诅咒的监狱里流淌着囚人们的歌谣。
将罪恶的青春全部抹杀殆尽。
“看守”执掌“囚犯”的生杀大权。
“囚犯”中藏着可以杀掉“看守”的恶魔。
这就是,将人性扭曲的,“监狱游戏”。
监狱游戏的参加者被分为了看守和囚犯,两侧各n人。举行监狱游戏的地点在一个被改造过了的大仓库一样的地方
,里面有两排共2n个房间,服务入口侧的是囚犯的房间,行刑室侧的是看守的房间。如下图所示。
相邻的看守与囚犯的房间之间可以通过对讲机互相沟通,但是声音会被处理,无法辨别。两侧的分类房中都有一排
各n扇门,从左到右编号为1~n。进入一扇门之后会有一条狭长、黑暗,而且弯弯曲曲的走廊通向房间。由于其特
殊的构造,看守的i号门对应房间未必就是囚犯的i号门对应的房间。因此,想在这个监狱游戏中胜出,了解门与门
之间的对应关系是很有必要的。接下来的问题就和监狱游戏没有太多关系了。我们令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
2 4
2 5
1 4
3 4
3 5
Sample Output
3
分析:在图上求独立集与覆盖集好像是没有多项式解法的. 既然要求方案数,那么肯定要用到dp或数学方法. 因为逆序图和序列是一一对应的,所以可以先把逆序图转化为序列,在序列上来处理.
图中编号为 i 的节点即为序列中的第 i 个元素,一条边则代表一个逆序对。那么点 i 与所有编号大于 i 的点之间连的边数,就是序列中第 i 个数后面比 i 小的元素的个数。
于是我们就得到了一个算法。从左到右考虑序列的每一个元素 i,求出点 i 与编号大于i 的点之间连的边数,假设为 k,那么 a[i] 就是当前尚未被选择的第 k + 1 小的数。这个算法的复杂度为O(n^2).
怎么求方案数呢?
令f[i]表示以i结尾的符合要求的子序列有多少个. f[i] += f[j] (j < i). 必须满足要求:a[j] < a[i],a[k] < a[j]或a[k] > a[i](j < k < i). 这样元素i,j在图上就既是独立集,也是覆盖集了.
这样复杂度是O(n^3)的,怎么优化呢? 固定左端点i,动态维护a[k]的最小值,往右枚举a[j]即可.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 1010,mod = 1e9+7; int n,m,vis[maxn],du[maxn],a[maxn],f[maxn]; int main() { scanf("%d%d",&n,&m); for (int i = 1; i <= m; i++) { int x,y; scanf("%d%d",&x,&y); if (x < y) du[x]++; else du[y]++; } for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) { if (vis[j]) continue; if (!du[i]) { a[i] = j; vis[j] = 1; break; } du[i]--; } a[0] = 0; a[n + 1] = n + 1; f[0] = 1; for (int i = 0; i <= n; i++) { int temp = n + 1; for (int j = i + 1; j <= n + 1; j++) { if (temp < a[j] || a[i] > a[j]) continue; f[j] += f[i]; f[j] %= mod; temp = min(temp,a[j]); } } cout << f[n + 1] << endl; return 0; }