SEERC 2018 I. Inversion
题目链接
http://codeforces.com/gym/101964/problem/I
题目大意
用一个 $1$ 到 $n$ 的一个数列的一个排列来构图。在数列中所有的逆序对的点对中建边。现在给你这个图的所有边,问你一共有多少种点的集合,使得这个集合中任意两个点都没有边相连,且其余所有点都最少与这个集合中的一个点相连。
题目分析
根据题目的提示,考虑可能需要将图还原成数列进行分析。我们考虑从这个数列中选出若干个点作为这个集合,那么这个集合应具备以下几个性质:
- 这若干个数应该是升序排列的,否则会出现逆序对出现连边。
- 对于这些数的右边的所有的点,应当都比选出来的最大的数(选出来的数的最后一个数)要小,这样才会出现逆序对,与点集有连边。
- 对于其他的不属于这个集合的点,应当不存在一个点,使得加入这个点后,这个选出来的数所组成的数列仍保持升序。因为如此的话应当是不存在逆序对的。
根据这性质 $1, 3$,可以进行 $DP$,计算出以每个点结尾的满足条件的升序数列有多少种情况,之后用性质 $2$ 进行校验即可。
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
const int maxn = 100 + 5;
int n, m;
int cnt[maxn];
int a[maxn];
long long f[maxn];
int maxx[maxn];
int read(){
int x;
scanf("%d", &x);
return x;
}
int main(){
int i, j;
int x, y;
long long ans = 0;
n = read();
m = read();
for(i=1;i<=m;i++){
x = read();
y = read();
cnt[max(x, y)]++;
}
for(i=n;i>0;i--){
int t = 0;
for(j=n;j>0;j--){
if(!a[j]){
if(t == cnt[i]){
a[j] = i;
break;
}else{
t++;
}
}
}
}
for(i=n;i>=1;i--){
maxx[i] = max(maxx[i + 1], a[i]);
}
f[0] = 1;
for(i=1;i<=n;i++){
int t = 0;
for(j=i-1;j>=1;j--){
if(a[j] < a[i]){
t = max(t, a[j]);
if(a[j] >= t){
f[i] += f[j];
}
}
}
if(f[i] == 0){
f[i] = 1;
}
}
for(i=1;i<=n;i++){
if(a[i] > maxx[i + 1]){
ans += f[i];
}
}
printf("%lld\n", ans);
return 0;
}