一、题目
定义字符集为 \(0\sim9,a\sim z,A\sim Z\) 共 \(62\) 种不同的字符,现在给你一个长度为 \(n\) 的字符串。
有 \(m\) 次操作,第 \(i\) 个操作表示把所有 \(x_i\) 的字符变成 \(y_i\),请问在要求每个操作至少执行一次的情况下,最终字符种类的最大值是多少?
\(n\leq 1000,m\leq 62\times 61\)
二、解法
我们把字符看成点,操作看成边建图。首先考虑一个基本的 \(\tt observation\):如果一个点操作了它的某一个出边,那么剩下的出边就不需要操作了。你可能觉得这个垃圾转化没什么币用,但是它其实把边的限制转化到了点上,也就是把边覆盖问题转化成了点覆盖问题,我曾经说过你只有尽可能的简化性质,神秘的结论才会赐福于你,其此之谓也。
然后考虑原图中的一个路径集合 \(L\),定义它合法为经过了需要覆盖的点,定义它的权值为结尾在原串出现过的路径数量,证明最小权值为最小字符损失可以考虑一下两点:
- 若存在一个合法的 \(L\) 权值为 \(x\),那么可以找到一个存在损失不超过 \(x\) 的方案。
- 若存在一个造成 \(x\) 损失的方案,可以找到一个权值不超过 \(x\) 的 \(L\)
因为上面的结论不难感性理解我就不证了,我们来考虑如何求出最优的 \(L\),首先我们对原图强连通缩点。因为它的本质是最小链覆盖所以我们使用网络流 \(+\) 调整法,魔改一下模板建图即可:
- 左部为需要覆盖的强连通分量,需要覆盖即大小不为 \(1\) 或者出度不为 \(0\),并且在原串中出现过。
- 右部的一部分为需要覆盖的强连通分量,如果有边就把左部和右部的强连通分量连起来。另一部分为 \(0\) 的单点,也就是你可以把它作为路径的终点,如果能到达就把左部的强连通分量和它连边(在内部的也算能到达)
设 \(|A|\) 为左部集合的大小,那么初始损失量就是 \(|A|\),最小损失量是 \(|A|-flow\),那么答案自然可以计算了。
#include <cstdio>
#include <cstring>
#include <vector>
#include <stack>
#include <queue>
using namespace std;
const int M = 1005;
const int inf = 0x3f3f3f3f;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,Ind,cnt,d[M],mp[M],in[M],dfn[M],low[M],col[M];
int ans,tot,S,T,a[M],b[M],f[M],dis[M],to[M][M];char s[M];
vector<int> g[M];stack<int> st;
struct edge
{
int v,c,next;
}e[M*M];
void add(int u,int v,int c)
{
e[++tot]=edge{v,c,f[u]},f[u]=tot;
e[++tot]=edge{u,0,f[v]},f[v]=tot;
}
int bfs()
{
queue<int> q;q.push(S);dis[S]=1;
for(int i=1;i<=T;i++) dis[i]=0;
while(!q.empty())
{
int u=q.front();q.pop();
if(u==T) return 1;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(!dis[v] && e[i].c>0)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return 0;
}
int dfs(int u,int ept)
{
if(u==T) return ept;
int tmp=0,flow=0;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(dis[v]==dis[u]+1 && e[i].c>0)
{
tmp=dfs(v,min(ept,e[i].c));
if(!tmp) continue;
ept-=tmp;flow+=tmp;
e[i].c-=tmp;e[i^1].c+=tmp;
if(!ept) break;
}
}
return flow;
}
void tarjan(int u)
{
dfn[u]=low[u]=++Ind;
st.push(u);in[u]=1;
for(auto v:g[u])
{
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
int v;cnt++;
do{
v=st.top();st.pop();
b[cnt]|=a[v];
col[v]=cnt;in[v]=0;
}while(v^u);
}
}
int main()
{
freopen("machine.in","r",stdin);
freopen("machine.out","w",stdout);
for(int i='0';i<='9';i++) mp[i]=++m;
for(int i='a';i<='z';i++) mp[i]=++m;
for(int i='A';i<='Z';i++) mp[i]=++m;
scanf("%s",s+1),n=strlen(s+1);k=read();
for(int i=1;i<=n;i++) a[mp[s[i]]]=1;
for(int i=1;i<=m;i++) ans+=a[i];
while(k--)
{
scanf("%s",s);to[mp[s[0]]][mp[s[1]]]=1;
g[mp[s[0]]].push_back(mp[s[1]]);
}
for(int k=1;k<=m;k++)
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
to[i][k]|=to[i][j]&to[j][k];
for(int i=1;i<=m;i++)
if(!dfn[i]) tarjan(i);
S=0;T=3*m+1;tot=1;
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
if(to[i][j] && b[col[i]] && !a[j])
add(col[i],j+2*m,1);
for(int u=1;u<=m;u++)
for(auto v:g[u]) if(col[u]^col[v])
add(col[u],col[v]+m,1),d[col[u]]++;
for(int i=1;i<=m;i++) if(b[i] && d[i])
ans--,add(S,i,1),add(i+m,T,1);
for(int i=1;i<=m;i++) if(!a[i])
add(2*m+i,T,1);
while(bfs()) ans+=dfs(S,inf);
printf("%d\n",ans);
}