JZOJ 6085. 【GDOI2019模拟2019.3.26】要换换名字(二分+Trie+二分图匹配)

JZOJ 6085. 【GDOI2019模拟2019.3.26】要换换名字

题目大意

  • 给出 n n n个由小写字母组成的字符串,每个串用它的某个非空子序列替代它,求使得替代后所有串互不相同的最长串最小长度。若不存在则输出 − 1 -1 1
  • 1 ≤ n , l e n ≤ 300 1\le n,len\le300 1n,len300

题解

  • 先二分答案,给每个串找出长度小于 m i d mid mid n n n个子序列,如果不足 n n n个则找出所有子序列。任意找 n n n个即可,因为只要有 n n n个就能使得不出现重复。
  • 既然已经找出来了每个串替换为什么子序列,那么把它们连边,跑一次二分图匹配,若能得到最大匹配则符合条件,否则不符合。
  • 要注意在这里不同串找出的子序列可能会相同,可以放在Trie上去重。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 310
#define ll long long
char a[N][N];
int ch[N * N], vi[N * N];
int ls[N], f[N][N][26];
int last[N], nxt[N * N], to[N * N], len;
int last1[N * N * 2], nxt1[N * N], to1[N * N], len1;
int n, tot, sum, tr = 1;
struct node {
	int a[N];
}st[N * N], s0[N], c;
struct {
	int p[26];
}T[N * N * 2];
void add1(int x, int y) {
	to1[++len1] = y;
	nxt1[len1] = last1[x];
	last1[x] = len1;
}
void add(int x, int y) {
	to[++len] = y;
	nxt[len] = last[x];
	last[x] = len;
}
void dfs(int k, int x, int s, int ln, int o) {
	if(tot == n) return;
	if(s > 0) tot++, add1(o, k);
	if(s < ln) {
		for(int i = 0; i < 26 && tot < n; i++) if(f[k][x][i] > 0) {
			int o0;
			if(T[o].p[i]) o0 = T[o].p[i]; else o0 = T[o].p[i] = ++tr;
			dfs(k, f[k][x][i], s + 1, ln, o0);
		}	
	}
}
void goes(int k) {
	if(last1[k]) {
		st[++sum] = c;
		for(int i = last1[k]; i; i = nxt1[i]) add(to1[i], sum);
	}
	for(int i = 0; i < 26; i++) if(T[k].p[i]) {
		c.a[++c.a[0]] = i;
		goes(T[k].p[i]);
		c.a[0]--;
	}
}
int solve(int k, int id) {
	for(int i = last[k]; i; i = nxt[i]) if(vi[to[i]] < id) {
		vi[to[i]] = id;
		if(!ch[to[i]] || solve(ch[to[i]], id)) {
			ch[to[i]] = k;
			return 1;
		}
	}
	return 0;
}
int main() {
	int i, j, k;
	scanf("%d", &n);
	memset(f, 255, sizeof(f));
	for(i = 1; i <= n; i++) {
		scanf("%s", a[i] + 1);
		ls[i] = strlen(a[i] + 1);
		for(j = ls[i] - 1; j >= 0; j--) {
			for(k = 0; k < 26; k++) f[i][j][k] = f[i][j + 1][k];
			f[i][j][a[i][j + 1] - 'a'] = j + 1;
		}
	}
	int l = 1, r = 300, ans = -1;
	while(l <= r) {
		int mid = (l + r) / 2;
		memset(last, 0, sizeof(last));
		memset(last1, 0, sizeof(last1));
		len1 = len = sum = 0;
		for(i = 1; i <= n; i++) {
			tot = 0;
			dfs(i, 0, 0, mid, 1);
		}
		goes(1);
		memset(ch, 0, sizeof(ch));
		memset(vi, 0, sizeof(vi));
		int mat = 0;
		for(i = 1; i <= n; i++) if(solve(i, i)) mat++;
		if(mat == n) {
			r = mid - 1;
			ans = mid;
			for(i = 1; i <= sum; i++) if(ch[i]) s0[ch[i]] = st[i];
		}
		else l = mid + 1;
	}
	printf("%d\n", ans);
	if(ans > 0) {
		for(i = 1; i <= n; i++) {
			for(j = 1; j <= s0[i].a[0]; j++) printf("%c", 'a' + s0[i].a[j]);
			puts("");
		}
	}
	return 0;
}

自我小结

  • 这题一开始并没有任何像正解的思路,所以只写了随机化和贪心,但只能过小数据和纯随机的数据。
  • 有想到过二分,但不知道如何判断可行。只要想到“只需搜出 n n n个子序列”,便能容易想到接下来的做法。
posted @ 2021-03-31 16:06  AnAn_119  阅读(72)  评论(0)    收藏  举报