[GDOI模拟2015.08.12]囚人的旋律

题目大意

给定一个点数为 n ,边数为m的图 G=(V,E) ,改图的生成m满足存在一个 1 n的排列 a1..an ,使得:
(u,v)E,u<v ,满足 au>av
1i<jn,ai<aj 满足 (i,j)E
说简单点就是该图由一个排列中的逆序对位置相连而成。
求该图中有多少非空点集,满足该点集同时为该图的独立集和覆盖集。

1n1000,0mn(n1)2

题目分析

一般的独立集问题应该是NP问题。如果图是二分图,那么可以用最小覆盖集来解决。
但是在这题,题目中的图并不是二分图。所以将这题看成图来想是几乎不可解的(虽说我一看到独立集、覆盖集就有些小激动了)。
既然出题人给定了通过排列构造的图,那我们也应该将这题的图看成排列来想。
先考虑如何还原这个排列。我比赛时想了一个极其笨重的方法。就是将排列中每个数往比它小的数连边,然后做拓扑排序,依次从大到小填数字。
具体实现就是
1. 1u<vn,(u,v)E ,这两个位置上的数在原排列一定是正序的,所以连有向边 (v,u)
2. 1u<vn,(u,v)E ,这两个位置上的数在原排列一定是逆序的,所以连有向边 (u,v)
然后我们做拓扑排序,拓扑序越前的位置,对应的数越大。
容易证明上述方法的正确性,由此也可以证明给定的图对应的合法排列只有一个。
这时我们可以转化问题了。我们发现所谓独立集,其实就是一段数互不为逆序对,那么就肯定是数列中的递增子序列。而所谓覆盖集,其实就是除了集合内位置上的数,其它位置上的数与集合内至少一个位置上的数成逆序对。
我们利用数形结合的思想考虑一下:
由排列构成的平面直角坐标系
这是由排列构成的平面直角坐标系,横轴表示位置,纵轴表示值。先满足独立集的要求,即取递增的点,例如图中红点。接着满足覆盖集。我们发现能与一个点构成逆序对的点一定在该点左上或右下方。所以一个不属于独立集的点,如果在独立集中至少有一个逆序点,那它必须在图中黄色区域,这种区域由所有红点的左上角和右下角构成。
这样,独立集的相邻两点构成了许多长方形(这是独立集内点递增的好处)。我们再次转化上面所述的条件,就可以得到,一个独立集如果是覆盖集,当且仅当坐标系中没有任何非该集合的点位于白色区域。
于是题目转化为,求有多少递增子序列,相邻两点构成的长方形块内没有点(注意特殊考虑第1个点和第n个点的情况)。
这时我们增加点 (0,0) (n+1,n+1) ,我们用动态规划解决这个问题。
fi 表示当前考虑到位置 i (集合内所有数位置小于等于i),包括 i 点的独立覆盖集的个数。

suml,x,r,y(0lrn+1,0xyn+1)
表示坐标系中左下顶点坐标为 (l,x) ,右上顶点坐标为 (r,y) 的长方形内点的个数。这个数组可以用二维前缀和处理。
显然可得

fi=j=0j1{fj|aj<ai,sumj,aj,i,ai=2}

初值 f0=1 ,最后答案为 fn+1

很高兴这道题在比赛上一次过了。让我开心一会。
其实这题主要就是考察模型的转化,还有一定的归纳总结。接着就是一个水dp了。
这是我在CSDN第一个博客,想写点有意义的题(然而对各位神犇来说还是太水了)。请大家多多关照。

代码实现

#include <iostream>
#include <cstdio>

using namespace std;

const int N=1005;
const int M=(N-1)*N>>1;
const int P=1000000007;

int tov[M+1],next[M+1],f[N+1],a[N+1];
int last[N+1],que[N+1],deg[N+1];
int n,m,tot,ans,ord,head,tail;
bool edge[N+1][N+1];
int sum[N+1][N+1];

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9')
    {
        if (ch=='-')
            f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}

void insert(int x,int y)
{
    tov[++tot]=y;
    next[tot]=last[x];
    last[x]=tot;
    deg[y]++;
}

int main()
{
    freopen("senritsu.in","r",stdin);
    freopen("senritsu.out","w",stdout);
    n=read(),m=read();
    int x,y;
    for (int i=1;i<=m;i++)
    {
        x=read(),y=read();
        edge[x][y]=true;
        edge[y][x]=true;
        if (x<y)
            insert(x,y);
        else
            insert(y,x);
    }
    for (int i=1;i<n;i++)
        for (int j=i+1;j<=n;j++)
            if (!edge[i][j])
                insert(j,i);
    head=0;
    tail=1;
    for (int i=1;i<=n;i++)
        if (!deg[i])
        {
            que[1]=i;
            break;
        }
    ord=n;
    int i;
    while (head!=tail)
    {
        x=que[++head];
        a[x]=ord--;
        i=last[x];
        while (i)
        {
            y=tov[i];
            deg[y]--;
            if (!deg[y])
                que[++tail]=y;
            i=next[i];
        }
    }
    a[0]=0;
    f[0]=1;
    a[n+1]=n+1;
    int tmp,get,j;
    sum[0][0]=1;
    for (i=1;i<=n;i++)
        sum[i][a[i]]=1;
    sum[n+1][n+1]=1;
    for (i=0;i<=n+1;i++)
        for (j=1;j<=n+1;j++)
            sum[i][j]+=sum[i][j-1];
    for (i=1;i<=n+1;i++)
        for (j=0;j<=n+1;j++)
            sum[i][j]+=sum[i-1][j];
    for (i=1;i<=n+1;i++)
        for (j=0;j<i;j++)
        {
            if (a[i]<a[j])
                continue;
            tmp=sum[i][a[i]]-sum[i][a[j]]-1;
            get=sum[j][a[i]]-sum[j][a[j]];
            tmp-=get;
            if (!tmp)
                f[i]=(f[i]+f[j])%P;
        }
    printf("%d\n",f[n+1]);
    fclose(stdin);
    fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值