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