CCPC2021 女生专场 攻防演练 和 test20200528 要换换名字
攻防演练
小Q和小C在比特公司的系统中进行攻防演练,这个系统经过特殊设定,只能接收任何只含有前 \(m\) 个小写英文字母的非空字符串作为输入命令。
小Q事先准备了一个长为 \(n\) 的字符串 \(s = s_1 s_2 \ldots s_n\),为了能够在演练时输入到系统中,这个字符串只会包含前 \(m\) 个小写英文字母。攻防演练一共进行 \(q\) 轮,每轮开始时小Q会选择一个 \(s\) 的非空子串 \(s_{l,r} = s_l s_{l+1} \ldots s_r\) 输入到系统中作为本轮演练的防火墙规则。当防火墙规则配置完成之后,对于任何输入到系统中的命令 \(c\),只要 \(c\) 是 \(s_{l,r}\) 的子序列就会被防火墙拦截。
小C的任务是在每轮演练中,在小Q完成本轮防火墙规则的配置之后,输入一个不被防火墙拦截的命令。为了节约时间,小C想知道每轮演练需要输入的最短字符串的长度是多少。也就是说,小C想找到最小的正整数 \(k\),存在一个长为 \(k\) 的字符串 \(t = t_1 t_2 \ldots t_k\),使得不存在任意一个长为 \(k\) 的序列 \(p_1, p_2, \ldots p_k\) 满足 \(l \le p_1 < p_2 < \ldots < p_k \le r\) 且对每个 \(i=1,2,\ldots,k\) 均有 \(t_i = s_{p_i}\)。
\(1 \le m \le 26, 1 \le n \le 200\,000, 1 \le q \le 200\,000\)
题解
我的第一个想法是答案不会太大,因为长度为\(l\)的子序列种类数最多是\(\binom{n}{l}\),而长度为\(l\)的字符串总数是\(m^l\),只需要建出子序列自动机然后对于询问\([l, r]\)从结点\(l-1\)开始暴力BFS就行了。
然后我想了想他怎么卡我,发现只需要构造形如abcd...zabcd...zabcd...z...的字符串,即26个字母各出现一次然后循环就行了。这样答案会达到\(n/m\)级别。
但是这就是关键,那就是只有26个字母都出现一次后答案才能加一。
那么可以以此设计跳跃关系。每个结点往后跳到之后26个字母最近出现位置的最远者。
使用倍增优化时间复杂度,\(O(nm+(n+q)\log n)\)。
constexpr int N=2e5+10;
char s[N];
int nx[N][26], to[N][18];
int main(){
int m=read<int>(), n=read<int>();
scanf("%s", s+1);
fill(nx[n], nx[n]+m, n+1);
for(int i=n-1; i>=0; --i){
copy(nx[i+1], nx[i+1]+m, nx[i]);
nx[i][s[i+1]-'a']=i+1;
}
for(int i=n; i>=0; --i){
int mx=0;
for(int j=0; j<m; ++j) mx=max(mx, nx[i][j]);
to[i][0]=mx;
// cerr<<i<<" to="<<to[i][0]<<endl;
for(int j=1; j<=17; ++j) to[i][j]=to[to[i][j-1]][j-1];
}
for(int q=read<int>(); q--; ){
int l=read<int>(), r=read<int>();
int p=l-1, ans=0;
for(int i=17; i>=0; --i)
if(to[p][i] && to[p][i]<=r) p=to[p][i], ans+=1<<i;
printf("%d\n", ans+1);
}
return 0;
}
要换换名字
从前有\(n\)个小朋友,他们有着令人印象深刻的很长的名字,比如cqqqqqqwqakioi。拥有很长的名字固然是一件振奋人心的大好事,但名字太长不方便被人膜拜,所以他们决定要换换名字。
这些小朋友很喜欢自己原来的名字,所以希望新名字是原来名字的非空子序列。例如,jiangkangping可以把名字改为jkp。
同时,两个名字相同的小朋友是非常令人困惑的,所以这些新名字必须两两不同。
请你为小朋友们换换名字,使得新名字中最长的尽量短。
请注意:虽然新名字要求两两不同,但旧名字可能相同。新名字也可以与旧名字相同。
对于100%的数据,\(2\leq n\leq 300\),\(1\leq \text{length}\leq 300\)。
题解
先二分答案\(\text{mid}\)。
然后我们只要对于每个小朋友都能找到\(n\)个\(\leq \text{mid}\)的子序列的话,就一定能够匹配上。证明考虑Hall定理。
找子序列可以在子序列自动机上BFS。
这样一来,只需要进行\(O(\log n)\)次点数为\(n^2\)的二分图匹配。时间复杂度\(O(n^3\log n)\)。
CO int N=310,inf=1e9;
int n,a[N][N],len[N];
int ans[N];
string plan[N];
namespace Trie{
int ch[N*N][26],dep[N*N],tot=1;
vector<int> v[N*N];
string s;
int insert(int x,int c){
if(!ch[x][c]){
ch[x][c]=++tot;
dep[ch[x][c]]=dep[x]+1;
}
return ch[x][c];
}
void dfs(int x){
for(int i:v[x]) plan[i]=s;
for(int i=0;i<26;++i)if(ch[x][i]){
s.push_back(i+'a');
dfs(ch[x][i]);
s.pop_back();
}
}
void read(){
for(int i=1;i<=n;++i) v[ans[i]].push_back(i);
dfs(1);
}
}
vector<int> mat[N];
namespace Match{
int nxt[N][N][26];
void bfs(int x){
deque<pair<int,int> > que={{1,0}};
int tot=0;
while(que.size() and tot<=n){
pair<int,int> p=que.front();que.pop_front();
if(p.first>1) mat[x].push_back(p.first);
for(int i=0;i<26;++i)
if(nxt[x][p.second+1][i]<=len[x]){
que.push_back({Trie::insert(p.first,i),nxt[x][p.second+1][i]});
++tot;
}
}
while(que.size()){
mat[x].push_back(que.front().first);
que.pop_front();
}
}
void solve(){
for(int i=1;i<=n;++i){
fill(nxt[i][len[i]+1],nxt[i][len[i]+1]+26,len[i]+1);
for(int j=len[i];j>=1;--j){
copy(nxt[i][j+1],nxt[i][j+1]+26,nxt[i][j]);
nxt[i][j][a[i][j]]=j;
}
}
for(int i=1;i<=n;++i) bfs(i);
}
}
namespace Flow{
int S,T;
struct edge {int y,c,a;};
vector<edge> to[N*N];
int dis[N*N];
IN void init(int n){
S=n-1,T=n;
for(int i=1;i<=n;++i) to[i].clear();
}
IN void link(int x,int y,int c){
to[x].push_back({y,c}),to[y].push_back({x,0});
to[x].back().a=to[y].size()-1,to[y].back().a=to[x].size()-1;
}
bool bfs(){
fill(dis+1,dis+T+1,inf),dis[S]=0;
deque<int> que={S};
while(que.size()){
int x=que.front();que.pop_front();
for(CO edge&e:to[x])if(e.c and dis[e.y]>dis[x]+1){
dis[e.y]=dis[x]+1;
que.push_back(e.y);
}
}
return dis[T]<inf;
}
int dfs(int x,int lim){
if(x==T) return lim;
int rest=lim;
for(edge&e:to[x])if(e.c and dis[e.y]==dis[x]+1){
int delta=dfs(e.y,min(rest,e.c));
if(!delta) {dis[e.y]=inf; continue;}
rest-=delta,e.c-=delta,to[e.y][e.a].c+=delta;
assert(to[e.y][e.a].y==x);
if(!rest) break;
}
return lim-rest;
}
int dinic(){
int ans=0;
while(bfs()) ans+=dfs(S,inf);
return ans;
}
int solve(int k){
init(n+Trie::tot+2);
for(int i=1;i<=n;++i) link(S,i,1);
for(int i=2;i<=Trie::tot;++i)if(Trie::dep[i]<=k) link(i+n,T,1);
for(int i=1;i<=n;++i)for(int x:mat[i])
if(Trie::dep[x]<=k) link(i,x+n,1);
return dinic();
}
void copy_ans(){
for(int i=1;i<=n;++i)for(CO edge&e:to[i])
if(e.c==0 and e.y>n) {ans[i]=e.y-n; break;}
}
}
int main(){
freopen("name.in","r",stdin),freopen("name.out","w",stdout);
read(n);
for(int i=1;i<=n;++i){
static char s[N];scanf("%s",s+1);
len[i]=strlen(s+1);
for(int j=1;j<=len[i];++j) a[i][j]=s[j]-'a';
}
Match::solve();
if(Flow::solve(300)<n){
puts("-1");
return 0;
}
int l=1,r=300;
while(l<r){
int mid=(l+r)>>1;
Flow::solve(mid)>=n?r=mid:l=mid+1;
}
printf("%d\n",l);
Flow::solve(l);
Flow::copy_ans();
Trie::read();
for(int i=1;i<=n;++i) printf("%s\n",plan[i].c_str());
return 0;
}