这里不细讲 AC 代码,这篇题解主要是教像我这种连 bitset + 拓扑排序都不会的蒟蒻如何用暴力模拟在比赛中拿到 40 分。

思路

首先,容易想到,我们可以把一条龙抽象成左边是 ll ,右边是 rr 的一个区间,但是因为龙里的数是从大到小,所以,一般情况下 lrl \ge r

合并也很简单,龙 bb 可以接在龙 aa 后面,当且仅当 aar=br = bl+1l + 1,合并之后的龙的 l=al = allr=br = brr

所以,当我们读入数堆里的一个数 xx 时,先把它作为一条 l=r=xl = r = x
的龙,如果它可以和上一条龙接在一起,就把它接在一起;否则就把它作为一条新龙丢到 vector 末尾。

此外,我还加了一个细节:当 xx 是读入的数里面的第一个数时,先往
vector 里丢一个 l=xl = xr=x+1r = x + 1 的龙,可以和 xx 照常接在一起,以免出现在 vector 里面找不到数的情况。

这里还要特别提醒一下,题目里有一句话,是这样的:

一组连续的牌只能移动到一个非空牌堆的最右边……

这意味着,这道题不能像蜘蛛纸牌那样,用空牌堆来转移!!!

所以,我们可以高兴的继续写暴力了!!!

因为接总是把两条龙接成一条,所以如果有解,把 kk 条龙接成一条一定需要 k1k - 1 次操作,所以这道题的 4040 pts 就变成了求龙数量和是否有解两个问题。

求龙数量很简单,只要是在输入第一个数或者新加龙的时候统计一下即可。

而判是否有解我们可以这样:

枚举每一个牌堆最顶上的龙,判断它要接的那条龙是否在牌堆最末端,如果在最末端就把它接上去,否则不能接。因为每一条龙只能接到一条龙的后面,影响不到其他的龙,所以先接哪条后接哪条都一样!!!

思路已经想好了,那该如何实现呢?

暴力的方法就是寻找每个牌堆的最末端,时间复杂度达 O(nm)O(nm),超时。

但是我们可以用 f[i]f[i]d[i]d[i] 分别记录结尾为 ii 的龙所在的牌堆号和结尾为 ii 的龙在所在的牌堆里的位置,这样,就可以 O(1)O(1) 判断要接的那条龙是否在末尾了。

如果在这 mm 个牌堆里的最后一条龙都无法接到其他龙,则无解,否则,有解。

时间复杂度大概是 O(n)O(n)4040 分稳稳到手!

代码

4040 pts 代码:

#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;

struct lo{
	int l, r;
	bool operator&(lo a){
		return r == a.l + 1;
	}
	lo operator+(lo a){
		return (lo){l, a.r};
	}
	void operator+=(lo a){
		*this = *this + a;
	}
}; // 区间结构体
int n, m, k, t, tt, d[50001], f[50001], did = 1;
vector<lo> g[50001];
int main(){
	scanf("%d%d%d", &t, &n, &m);
	for (int i = 1;i <= m;i ++){
		scanf("%d", &t);
		for (int j = 1;j <= t;j ++){
			scanf("%d", &tt);
			if (j <= 1) g[i].push_back((lo){tt, tt + 1}); // 塞入一条没用的龙
			lo awa = {tt, tt}, end = *(-- g[i].end());
			if (end & awa) *(-- g[i].end()) += awa; // 可以接就接
			else d[end.r] = g[i].size(), f[end.r] = i, g[i].push_back(awa), k ++; // 否则,记录上一条龙的信息,新增一条龙,并计数。
		}
		if (!t) continue;
		lo end = *(-- g[i].end());
		d[end.r] = g[i].size(), f[end.r] = i, k ++; // 记录信息并计数。
	}
	for (int _ = 1;_ < k;){
		did = 0;
		for (int i = 1;i <= m;i ++){
			if (!g[i].size()) continue; // 防止 RE
			lo end = *(-- g[i].end());
			int sr = end.l + 1; // 求出要接到的龙的 r
			if (sr > n) continue; // l == n 的龙不能接
			int tr = end.r; // 自己的 r
			int sf = f[sr]; // 求出要接到的龙所在的牌堆
			if (d[sr] == g[sf].size()){ // 如果正好就在末尾,就接上去
				*(-- g[sf].end()) += end; // 合并
				g[i].pop_back(); // 把原来的删掉
				d[tr] = d[sr];
				f[tr] = sf;
				d[sr] = f[sr] = 0; // 更改信息
				did = 1; // 已经接过了,记录并非无解
				_ ++; // 已经操作了一次
			}
		}
		if (!did) break; // 如果无解
	}
	if (did) printf("YES\n%d\n", k - 1); // 有解
	else printf("NO\n"); // 无解
	for (int i = 1;i <= n;i ++) printf("0\n");
}