NOIp2013 模拟题 囚人的旋律
囚人的旋律
题意简述
对于长度为 \(n\) 的序列 \(a[]\) ,定义其逆序图 \(G\) 如下:无向图 \(G\) 有 \(n\) 个节点,编号为 \([1,n]\) ,对于任意的 \(1\le i<j \le n\) ,如果有 \(a[i]>a[j]\) ,那么 \(G\) 中存在一条 \(i\) 和 \(j\) 之间的边。给定一个逆序图 \(G\) ,求 \(G\) 有多少个点集即是独立集又是覆盖集。\(n\le 1000\) ,\(0\le m\le \frac{n(n-1)}{2}\) 。
\(\operatorname{Solution}\)
阴间 \(\operatorname{dp}\) 毁我青春\(...\)
看到这道题又是建图又是子集,考场上差点放弃\(...\)后来发现这道题实际上和图并没有关系。
- 独立集:对于选出来的子集 \(S\) 中的任意元素 \(u,v\) ,原图中不存在 \((u,v)\) 这条边。
- 覆盖集:对于任意不存在与 \(S\) 的一个元素 \(u\) ,存在 \(S\) 中的一个元素 \(v\) ,使得原图中存在 \((u,v)\) 这条边。
这么看好像挺复杂的\(...\) 考虑一下一条边 \((u,v)\) 是如何生成的。如果 \((u,v)\) 这条边存在,且 \(u<v\) ,那么一定有 \(a[u]>a[v]\) 。那图中的一条边是否存在其实就是 \(u\) 和 \(v\) 的大小是否满足逆序对关系。那满足条件的子集定义就可以翻译一下了:
- 独立集:对于 \(i\) ,如果其后面的一个元素 \(j\) 可以被加入集合,那么一定满足 \(a[i]<a[j]\) 。
- 覆盖集:对于 \(i\) ,如果其后面的一个元素 \(j\) 可以被加入集合,那么一定满足对于任意 \(k\in (i,j)\) ,满足 \(a[i]>a[k]\) 或 \(a[k]>a[j]\) 。
此时暴力的 \(\operatorname{dp}\) 状态就比较明显了。设 \(f[i]\) 为在 \(i\) 前面加入了若干元素,且第 \(i\) 个元素一定要被加入集合的方案数。有(\(j\) 要满足和 \(i\) 同时加入 \(S\) 的条件):\(f[i]=\sum f[j](j\in [0,i-1])\)。
在判断是否满足条件时需要 \(\operatorname{O(n)}\) ,枚举 \(i\) 需要 \(\operatorname{O(n)}\) ,枚举 \(j\) 需要 \(\operatorname{O(n)}\) ,总时间复杂度 \(\operatorname{O(n^3)}\) ,已经可以很漂亮的过 \(60pts\) 的数据了。
思考一下怎么优化。下面将 \(\operatorname{dp}\) 的视角稍微转换一下,考虑 \(i\) 可以转移到哪些 \(j\) (此时显然 \(j\in [i+1,n]\))。当然真正循环的时候要到 \(n+1\) 。为什么是 \(n+1\) ?因为并不能确定子集在什么时候会结尾\(...\) 如果提前算了结尾的话会把很多无意义状态算进去。
解释一下,如果有 \(a[i]<a[j]<a[k]\) 且 \(i<j<k\) ,如果只将 \(a[i]<a[j]\) 加入集合的话,对于 \(a[k]\) 事不存在逆序对的\(...\) (子集中并不存在 \(q<k\) 且 \(a[q]>a[k]\) ),状态就无意义了\(...\)
第一次 \(i\) 枚举 \(j\) 的时候循环了一遍 \((i,j)\) ,第二次枚举 \(j+1\) 的时候循环了一遍 \((i,j+1)\) ,可以发现 \((i,j)\) 这个区间被循环了两次,很浪费时间。显然可以通过递推优化 \(\operatorname{O(n)}\) 的时间复杂度。
具体的,对于每一个即将被判决的 \(j\) ,记录以最后一个对于 \(i\) 来说合法的 \(tmp\) ,如果 \(a[j]<a[tmp]\) ,那么 \(j\) 一定是合法的。
为什么呢?对于 \(k\in (tmp,j)\) ,一定满足 \(a[k]>a[tmp]\) ,又 \(a[j]<a[tmp]\) ,那么一定有 \(a[k]>a[tmp]>a[j]\) 。对于 \(k\in (i,tmp]\) ,分为两类:
- \(a[k]<a[i]\) :在加入 \(j\) 之后依然 \(a[k]<a[i]\)
- \(a[k]>a[tmp]\) :由于 \(a[j]<a[tmp]\) ,在加入 \(j\) 之后依然 \(a[k]>a[j]\)
完美覆盖了所有情况,\(j\) 一定是合法的。
代码简洁的令人害怕\(...\) \(g\) 数组用来存储边的关系,\(f\) 数组事 \(\operatorname{dp}\) 数组。
#include <bits/stdc++.h>
#define ll long long
#define INF 1e18
#define maxn 2001
#define mod 1000000007
using namespace std;
ll n,m;
ll u,v;
ll g[maxn][maxn];
ll f[maxn];
inline ll read(){
ll x=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return f?-x:x;
}
int main(){
freopen("senritsu.in","r",stdin);
freopen("senritsu.out","w",stdout);
n=read(),m=read();
for(register int i=1;i<=m;++i) u=read(),v=read(),g[u][v]=g[v][u]=1;
f[0]=1;
for(register int i=0;i<=n;++i){
ll tmp=0;
for(register int j=i+1;j<=n+1;++j)
if((!tmp||g[tmp][j])&&(!g[i][j]))
f[j]=(f[j]+f[i])%mod,tmp=j;
}
printf("%lld\n",f[n+1]%mod);
return 0;
}