BZOJ4715囚人的旋律 DP
Posted CQzhangyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BZOJ4715囚人的旋律 DP相关的知识,希望对你有一定的参考价值。
【BZOJ4715】囚人的旋律
Description
我们令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
题解:首先,根据这个逆序图是可以在O(n^2)时间内将原序列还原出来的。
方法:统计每个数前面有多少个大于它的数pre,后面有多少个小于它的数nxt,那么最小的数i一定满足pre[i]=i-1&&nxt[i]=0,所以找出最小的数,然后将它删掉,更新其它点的pre和nxt。不断重复此过程即可。
那么本题求的东西到底是什么呢?一开始以为求的是边覆盖集,然后就变成了将原序列分成两个不相交的上升子序列,但是样例过不去。。。后来发现求的是点覆盖集。。。
因为选出来的序列是独立集,所以这个序列是递增的;又因为这是覆盖集,所以每个不在序列中的点都与某个序列中的点形成逆序对。所以我们求的就是极长上升序列的方案数!用f[i]表示前i个数中极长上升序列方案数,如果i是一个合法的结尾,则用f[i]更新答案即可。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int P=1000000007; int n,m,ans; int map[1010][1010],f[1010],nxt[1010],pre[1010],v[1010],bef[1010],aft[1010]; inline int rd() { int ret=0,f=1; char gc=getchar(); while(gc<‘0‘||gc>‘9‘) {if(gc==‘-‘) f=-f; gc=getchar();} while(gc>=‘0‘&&gc<=‘9‘) ret=ret*10+gc-‘0‘,gc=getchar(); return ret*f; } int main() { n=rd(),m=rd(); int i,j,k,a,b; for(i=1;i<=m;i++) { a=rd(),b=rd(); if(a>b) swap(a,b); map[a][b]=1,nxt[a]++,pre[b]++; } for(i=1;i<=n;i++) for(j=i+1;j<=n;j++) { if(!map[i][j]) bef[j]=aft[i]=1; } for(i=1;i<=n;i++) { for(k=1;k<=n;k++) if(!v[k]&&!nxt[k]&&pre[k]==k-1) break; v[k]=i; for(j=1;j<k;j++) if(map[j][k]) nxt[j]--; for(j=k+1;j<=n;j++) pre[j]++; } for(i=1;i<=n;i++) { f[i]=(f[i]+(!bef[i]))%P; for(k=n+1,j=i+1;j<=n;j++) if(!map[i][j]&&v[j]<k) k=v[j],f[j]=(f[j]+f[i])%P; if(!aft[i]) ans=(ans+f[i])%P; } printf("%d",ans); return 0; }
以上是关于BZOJ4715囚人的旋律 DP的主要内容,如果未能解决你的问题,请参考以下文章