[提高组集训2021] 麻将机

一、题目

定义字符集为 \(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);
}
posted @ 2021-11-16 19:49  C202044zxy  阅读(203)  评论(1编辑  收藏  举报