FJWC2019 不同的缩写 二分图匹配+trie

30 篇文章 0 订阅
15 篇文章 0 订阅

Description


你在写一款 Galgame 的剧情(的代码)。
在这个游戏中一共有 n 个角色。你需要编写一些关于这些角色的对话内容。然而,在写这些对话内容之前,都要写一段关于角色信息的代码,就像这样:
Character(“Alex”, color = “#FFFC3A”)
你觉得这样好麻烦。你决定把它简化一下。你打算用角色名字的一个非空子序列(可以不连续)来作为它的简称。
当然,不同的角色要用不同的字符串作为简称,否则你就变量重名了。
你想确定一个简称的分配方案使得所有角色中最长的简称尽量短,这样你打起代码就会方便一些。

保证 n ≤ 300 n \le 300 n300 ,每个名字的长度不超过 300。

Subtask 1(30 pts) : n ≤ 4 n \le 4 n4
Subtask 2(30 pts) : n ≥ 100 n \ge 100 n100 , 串长和串的内容在题目范围内均匀随机。即串长在 [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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值