zoukankan      html  css  js  c++  java
  • [提高组集训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);
    }
    
  • 相关阅读:
    swagger多个分组代码展示
    rabbitMQ基本概念
    谈谈微信支付曝出的漏洞
    谈谈离职和跳槽
    一个著名的任务调度系统是怎么设计的?
    BAT等公司必问的8道Java经典面试题,你都会了吗?
    从世界杯竞猜骗局谈二分法
    Spring4+Spring MVC+MyBatis整合思路
    十张图让你了解阿里公司架构设计的发展变化史
    一位00后程序员的成长历程
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15563207.html
Copyright © 2011-2022 走看看