bzoj4715 囚人的旋律

4715: 囚人的旋律

Time Limit: 10 Sec  Memory Limit: 512 MB
Submit: 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

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;
}
 
原文地址:https://www.cnblogs.com/zbtrs/p/8707297.html