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;
}