Description
你在写一款 Galgame 的剧情(的代码)。
在这个游戏中一共有 n 个角色。你需要编写一些关于这些角色的对话内容。然而,在写这些对话内容之前,都要写一段关于角色信息的代码,就像这样:
Character(“Alex”, color = “#FFFC3A”)
你觉得这样好麻烦。你决定把它简化一下。你打算用角色名字的一个非空子序列(可以不连续)来作为它的简称。
当然,不同的角色要用不同的字符串作为简称,否则你就变量重名了。
你想确定一个简称的分配方案使得所有角色中最长的简称尽量短,这样你打起代码就会方便一些。
保证 n ≤ 300 n \le 300 n≤300 ,每个名字的长度不超过 300。
Subtask 1(30 pts) :
n
≤
4
n \le 4
n≤4
Subtask 2(30 pts) :
n
≥
100
n \ge 100
n≥100 , 串长和串的内容在题目范围内均匀随机。即串长在 [1,300] 内随机,串的每一位在 aaa 到 zzz 之间随机。
Subtask 3(40 pts): 无特殊限制
Solution
感觉挺套路的,没有想象中难
30分很好做,我们迭代加深暴力搜就可以了,重复的判断可以直接map,然后偷懒用std:: string就行
60分也很好做,随机之后可以发现很大概率答案是2,因为2位已经可以选出很多子序列,而n最大才300,于是用30分的暴力从ans=2开始搜就可以了(事实证明这一部分的数据答案都是2
100分不太好做。一个很显然的套路就是我们二分答案,然后把所有串长度不超过mid的子序列都抠出来
考虑分配的含义,就是我们把原串和子序列建二分图,然后求最大匹配。存在完美匹配说明mid是一个上界
然后我们发现一个串的子序列是2^|S|级别的过不了。一个结论就是如果一个串能选出[至少n个长度不超过mid的][互不相同的]子序列,那么这个串一定能匹配,这个结论好像有点显然。。
于是我们只需要抠出至多n个互不相同的子序列就行了,我一开始写了hash+map被卡死,其实可以用特殊的构造使得短串都是长串的前缀,这样用trie来去重就去掉一个log了
Code
//#pragma GCC optimize(3)
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
#define rep(i,st,ed) for (register int i=st;i<=ed;++i)
#define drp(i,st,ed) for (register int i=st;i>=ed;--i)
#define fill(x,t) memset(x,t,sizeof(x))
const int INF=0x3f3f3f3f;
const int N=305;
const int E=N*N*4;
struct edge {int y,w,next;} e[E];
std:: string prt[E],ymw[N];
int dis[E],cur[E],len[N],chp[N];
int ls[E],que[E],tot,n,zyf,edCnt=1;
int tr[E][26],lzh[E],rec[E];
int yqw[N];
char str[N][N];
bool lxf;
inline void add_edge(int x,int y) {
e[++edCnt]=(edge) {y,1,ls[x]}; ls[x]=edCnt;
e[++edCnt]=(edge) {x,0,ls[y]}; ls[y]=edCnt;
}
inline bool bfs(int st,int ed) {
rep(i,st+1,ed) dis[i]=0; dis[st]=1;
int head=1,tail=1; que[1]=st;
for (;head<=tail;) {
int x=que[head++];
for (int i=ls[x];i;i=e[i].next) {
if (e[i].w&&dis[e[i].y]==0) {
dis[e[i].y]=dis[x]+1;
if (e[i].y==ed) return true;
que[++tail]=e[i].y;
}
}
}
return false;
}
inline int find(int x,int ed,int mn) {
if (x==ed||!mn) return mn;
register int ret=0;
for (register int &i=cur[x];i;i=e[i].next) {
if (e[i].w&&dis[x]+1==dis[e[i].y]) {
int d=find(e[i].y,ed,std:: min(e[i].w,mn-ret));
e[i].w-=d; e[i^1].w+=d; ret+=d;
if (mn==ret) break;
}
}
return ret;
}
int dinic(int st,int ed) {
int res=0;
for (;bfs(st,ed);) {
rep(i,st,ed) cur[i]=ls[i];
res+=find(st,ed,INF);
}
return res;
}
void get(int id,int mid) {
std:: string tmp1;
int cnt=1,tmp2=0;
rep(i,1,len[id]) drp(j,cnt,1) {
if (chp[j]+1>mid) continue;
if (lxf) tmp1=ymw[j]+str[id][i];
int ch=str[id][i]-'a';
if (!tr[yqw[j]][ch]) {
tr[yqw[j]][ch]=++zyf;
fill(tr[zyf],0);
}
tmp2=tr[yqw[j]][ch];
if (rec[tmp2]!=id) {
rec[tmp2]=id;
yqw[++cnt]=tmp2;
chp[cnt]=chp[j]+1;
if (lxf) ymw[cnt]=tmp1;
if (!lzh[tmp2]) {
lzh[tmp2]=++tot;
if (lxf) prt[tot]=tmp1;
}
add_edge(id,lzh[tmp2]+n);
}
if (cnt>=n+1) return ;
}
}
bool check(int mid) {
rep(i,0,n+tot+1) ls[i]=0;
rep(i,0,tot) lzh[i]=0;
rep(i,0,zyf) rec[i]=0;
fill(tr[0],0);
edCnt=1; tot=zyf=0;
rep(i,1,n) {
get(i,mid);
add_edge(0,i);
}
rep(i,1,tot) add_edge(i+n,tot+n+1);
int res=dinic(0,tot+n+1);
if (lxf) rep(x,1,n) {
for (register int i=ls[x];i;i=e[i].next) {
if (e[i].w||!e[i].y) continue;
for (register int j=0,__=prt[e[i].y-n].length();j<__;++j) {
putchar(prt[e[i].y-n][j]);
} puts(""); break;
}
}
return res>=n;
}
int main(void) {
freopen("data.in","r",stdin);
freopen("myp.out","w",stdout);
scanf("%d",&n); int mx=0;
rep(i,1,n) {
scanf("%s",str[i]+1);
len[i]=strlen(str[i]+1);
mx=std:: max(mx,len[i]);
}
int l=1,r=mx;
for (;l<=r;) {
int mid=(l+r)>>1;
if (check(mid)) r=mid-1;
else l=mid+1;
}
if (r==mx) return 0&puts("-1");
printf("%d\n", r+1);
lxf=1; check(r+1);
return 0;
}