其他分享
首页 > 其他分享> > JZOJ3483. 囚人的旋律

JZOJ3483. 囚人的旋律

作者:互联网

题目大意

给出一个由 \(n\)个数的排列, 逆序对之间连边 的方式构造出来的无向图, 求图中既是独立集又是覆盖集的点集的个数.
形式化的, 若\(i<j, p_i > p_j\), 则在图中连一条边\((i,j)\).

\(n <= 1000\).

解题思路

不难想到把图转化为序列上来做. 那么图中的边集转化成了序列上的一个子序列.
考虑计数既是独立集又是覆盖集的子序列的个数.

首先考虑独立集: 根据独立集中点之间没有边相连, 我们又有相连的边代表着一对逆序对, 说明在这个子序列中没有逆序对. 那是什么? 那是单调递增序列.

接下来考虑覆盖集: 假设相邻的选取的两个点是\(l, r\ (l < r)\). 由前面可知\(p_r > p_l\).

首先地, 在\((l,r)\)之间的点, 必定要么与\(l\)有边相连, 要么与\(r\)有边相连.
为什么? 自己手推一下不难发现, 这样的限制是最松的. 也就是说, 根据选出的子序列的单调性, 在\((l,r)\)中能与\(l\)之前的点或\(r\)之后的点连边的, 必能与\(l\)连边或与\(r\)连边.

然后, 注意到\((l,r)\)之间的点可分为两类. 一类是与\(r\)连边, 也就是\(p_i > p_r\), 一类是与\(l\)连边, 也就是\(p_i < p_l\).
于是不合法的点满足\(p_i\in (p_l, p_r)\).

分析出这些东西, 显然dp. 设\(f_i\)表示前\(i\)个数组成的子序列, 其中必选\(i\). 特别地, 设\(f_0=1\), 有如下转移式:

\[\Huge f_i=\sum_{j\in[0,i), \forall k \in (j,i) \text{满足}p_k \notin (p_j, p_i)}f_j \]

在实现中可以记录小于\(p_i\)的最大值, \(j\)指针从右往左扫, 顺便更新最大值和dp值, 过程中与\(p_j\)比较.

最后一个问题: 如何将给出的图转化为序列?
给出的图相当于给出了一堆偏序关系. 然而靠这些偏序关系并不能唯一确定这个序列. 所以其实言外还隐藏着 除了这些逆序对外, 不存在其他逆序对 的信息.
于是考虑连边拓扑, 按拓扑序 从大到小分配\(p_i\), 在拓扑排序中把队列换成大跟堆, 这样就能保证逆序对不增多.

时间复杂度\(O(n\log n+n^2)\), 瓶颈在于dp. 以及似乎有\(O(n)\)转化的方法, 在此不赘述.

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1010
#define MOD 1000000007
#define fo(i, a, b) for(int i = (a); i <= (b); ++i)
#define fd(i, a, b) for(int i = (a); i >= (b); --i)
using namespace std;
inline int read() // notice : 1. long long ? 2. negative ?
{
	int x = 0; char ch = getchar();
	while(ch < '0' || ch > '9')	ch = getchar();
	while(ch >= '0' && ch <= '9')	x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return x;
}
priority_queue<int> h;
int n, m, t[N][N], a[N], f[N], in[N];
void top_sort()
{
	fo(i, 1, n)	if(!in[i])	h.push(i);
	int p = n;
	while(!h.empty())
	{
		int u = h.top(); h.pop();
		a[u] = p--;
		fo(v, 1, n)	if(t[u][v])	if(!(--in[v]))	h.push(v);
	}
}
int main()
{
	n = read(), m = read();
	int u, v; 
	fo(i, 1, m)	u = read(), v = read(), t[min(u, v)][max(u, v)] = 1, ++in[max(u, v)];
	top_sort();
	f[0] = 1, a[n + 1] = 0x3f3f3f3f;
	fo(i, 1, n + 1)
	{
		int mx = -1;
		if(a[i] > a[i - 1])	f[i] = f[i - 1], mx = a[i - 1];
		fd(j, i - 2, 0)
		{
			if(a[i] > a[j] && a[j] > mx)	f[i] = (f[i] + f[j]) % MOD, mx = max(mx, a[j]);
		}
	}
	printf("%d\n", f[n + 1]);
	return 0;
}

标签:连边,ch,旋律,int,read,囚人,序列,JZOJ3483,逆序
来源: https://www.cnblogs.com/Martin-MHT/p/14731806.html