题目
给出长度为(n)的字符串(A) 和(B),求将(A)变为(B) 最少操作次数
一次操作为:
选择一些位置$p_1 , p_2 ,p_3 ,cdots ,p_m $ 满足(A_{p_1} =A_{p_2}=cdots =A_{p_m})
将他们统一替换为某个字母,保证(AB)均由前20个小写子母构成
$1 le n le 10^5 $
题解
将字母建成节点,连接有向边((A_i,B_i)) ,得到有向图(G)
问题即求最小的边数的新图(G'),使得保持(G)的连通性
不妨考虑(G)是一个弱连通图
设(D)是最大DAG子图,则(ans = 2|G| - 1 - |D|)
证明
- 设存在一个边数(k)的解,构造一个生成树,考虑剩下(k-n-1)可能会成环,因此每次至多删掉一个原图节点使为DAG。因此(|D| ge n-(k-n-1) = 2n - 1 - k) 即:(ans ge 2n-1-|D|)
- 考虑对于一个DAG子图D',可以按照拓扑序连成链,然后将非子图的点和链的首位相连,得到一个合法的答案。因此(ansle |D'|-1+2(n-|D'|) = 2n-1-|D'|) 即:(ans le 2n-1-|D|)
- 综上:(ans=2n-1-|D|)
通过dp计算最大DAG
复杂度:(O(N^2+M2^{M}))
#include<cstdio>
#include<iostream>
using namespace std;
const int N=100010,M=20;
int T,n,a[M],f[1<<M],cnt,ans,b[M][M],vis[M];
char S1[N],S2[N];
void adde(int u,int v){b[u][v]=b[v][u]=1;}
void dfs(int u){
vis[u]=1;
for(int i=0;i<20;++i)if(b[u][i]&&!vis[i])dfs(i);
}
int main(){
// freopen("E.in","r",stdin);
// freopen("E.out","w",stdout);
scanf("%d",&T);
while(T--){
scanf("%d%s%s",&n,S1,S2);
for(int i=0;i<20;++i){
a[i]=0;
for(int j=0;j<20;++j)b[i][j]=0;
}
for(int i=0;i<n;++i)if(S1[i]!=S2[i]){
a[S1[i]-'a']|=1<<(S2[i]-'a');
adde(S1[i]-'a',S2[i]-'a');
}
ans=cnt=0;
for(int i=0;i<20;++i)vis[i]=0;
for(int i=0;i<20;++i)if(!vis[i]){
cnt++;dfs(i);
}
f[0]=1;
for(int i=1;i<(1<<20);++i){
f[i]=0;
for(int j=0;j<20;++j)if(i&(1<<j)){
f[i] |= f[i^(1<<j)] && ((a[j]&i)==0);
if(f[i])break;
}
if(f[i])ans=max(__builtin_popcount(i),ans);
}
printf("%d
",40-cnt-ans);
}
return 0;
}//tkys_Austin