JZOJ 6085. 【GDOI2019模拟2019.3.26】要换换名字
题目大意
- 给出
n
n
n个由小写字母组成的字符串,每个串用它的某个非空子序列替代它,求使得替代后所有串互不相同的最长串最小长度。若不存在则输出
−
1
-1
−1。
-
1
≤
n
,
l
e
n
≤
300
1le n,lenle300
1≤n,len≤300
题解
- 先二分答案,给每个串找出长度小于
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
", 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个子序列”,便能容易想到接下来的做法。