bzoj 4715
其实我并没有见过原题,只是因为...这被改编成了互测题...
题目中提到了一个序列,这个序列是很重要的,否则这个问题好像是没有合理的时间复杂度解法的
但正因为有了这个序列,这个问题的时间复杂度才让人能够接受
序列的特性:逆序对!
根据题意,我们发现一个图上所有的连边一定来源于这个序列里的逆序对!
那么,如果要求一个点集内部没有连边,内部是不能有逆序对的!
那么这个条件等价于求出这个序列的上升子序列数目!
所以我们记dp[i]表示以i为结尾,获得所求点集的方案数
那么dp[i]就可以由dp[j]进行转移,其中j<i
但并不是所有的j都能转移到i,因为还有第二个约束条件
都要有连边怎么办?
我们发现,首先,如果想用f[j]来更新f[i],那么j~i之间的点都没有被使用,那这样一来我们就要让他们都与选中的点之间有连边
怎么做?
充要条件:对于任意j<k<i,有a[k]>a[i]或a[k]<a[j]
证明:首先我们知道,由于是上升序列,一定有:a[i]>a[j]
那么,如果要求都有连边,那么k要么会和i构成逆序对,要么会和j以内某个被选中的点构成逆序对
再考虑j以内所有值都比a[j]小,所以如果k能和j以内某个点构成逆序对,必然会和j构成逆序对
即要求:a[k]<a[j]
那么如果k与i构成逆序对,一定要求a[k]>a[i]
于是我们检验上述两个条件就好
可是这样做是O(n^3)过不了啊
再优化一下!
我们能够发现,如果有a[k]>a[i],根据单调性,一定有a[k]>a[j]!
而a[k]<a[j]的部分是已然成立的
所以我们仅需找出,对于所有a[k]>a[j]的k中最小的a[k]是否大于a[i]即可
而如果我们先枚举j,然后枚举i,那么是可以在枚举i的同时维护出这个最小的a[k]的!
这样时间就降到了O(n^2)
当然,我们忽略了一个问题:序列怎么求?
拓扑排序!
这里的拓扑稍特殊:需要用到优先队列,因为对于没有逆序对的部分,后面的一定比前面的大
最后贴代码:
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #define mode 1000000007 using namespace std; int dp[1005]; int inr[1005]; int maps[1005][1005]; bool used[1005]; int a[1005]; int n,m; int main() { freopen("is.in","r",stdin); freopen("is.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); if(x>y) { swap(x,y); } inr[y]++; maps[x][y]=maps[y][x]=1; } priority_queue <int> M; for(int i=n;i>=1;i--) { if(!inr[i]) { used[i]=1; M.push(i); } } int ttop=n; while(!M.empty()) { int u=M.top(); M.pop(); a[u]=ttop--; for(int i=n;i>=1;i--) { if(maps[u][i]) { inr[i]--; if(!inr[i]&&!used[i]) { used[i]=1; M.push(i); } } } } a[0]=0,a[n+1]=n+1; dp[0]=1; for(int j=0;j<=n;j++) { int minval=n+2; for(int i=j+1;i<=n+1;i++) { if(a[i]<a[j]||a[i]>=minval) { continue; } dp[i]+=dp[j]; dp[i]%=mode; minval=a[i]; } } printf("%d\n",dp[n+1]); return 0; }