[PA2015]Edycja

题目描述

给定两个字符串 a , b a,b a,b

每次可以执行下列两种操作之一:

  1. 花费 1 1 1 的代价使得 a a a 中某个位置的字符换成另一个。

  2. 可以花费 C C C 的代价使得 a a a 中所有某个字符换成另一个。

问把 a a a 变成 b b b 的最小代价。

解题思路

  • 性质1:一定存在一种方案使得所有 2 2 2 操作在 1 1 1 操作前面。

证明的话,考虑对于一个二操作和一操作,交换之后,只需要把 1 1 1 操作的值改成相应的值就行了。

  • 性质2:对于每个字符,最多会使用一次二操作。

proof \text{proof} proof

若使用超过一次二操作,如 a → b , a → c a\to b,a\to c ab,ac

那么因为执行完第一个后已经没有 a a a 了,所以第二次操作有效必定存在一个 d d d,满足: a → b , d → a , a → c a\to b,d\to a,a\to c ab,da,ac

那么不如 a → b , d → c a\to b,d\to c ab,dc

  • 性质3:若执行了一次二操作 a → b a\to b ab,那么之后不会再对 b b b 执行二操作。

因为若存在 b → c b\to c bc,可以直接改成 a → c a\to c ac

因此,对于每个字符,我们有且只会对其执行一次二操作,且由这次二操作产生的字符不会因为其他二操作改变,设其为 a → b a\to b ab ,也就是说若这次操作改变的位置有 x x x 个,其中有 y y y 个位置和最终字符串中相同,那么这次操作后需要花费 x − y x-y xy 个一操作,总花费为 x − y + C × [ a ≠ b ] x-y+C\times [a\neq b] xy+C×[a=b]

所以,若存在 a → b a\to b ab,那么我们就连一条从 a a a b b b 的边。

忽略自环,那么会形成一个内向基环树森林。

考虑一种贪心策略,为每个字符选择一个出边,使得 x − y + C × [ a ≠ b ] x-y+C\times [a\neq b] xy+C×[a=b] 最小。

这样构成一个图,若这个图没有环,那么存在一种构造是对于每棵树从上到下依次操作,同时因为贪心选择,所以总代价一定是最小的可以直接输出。

否则就有环,考虑怎么处理。

对于一棵基环树:

  • 若其不是一个环,那么必定存在 a → b a\to b ab,b是环上的点,如果环上还有 c → b c\to b cb,我们可以先执行 c → a c\to a ca,再依次执行环上的操作,最后执行 a → b a\to b ab,可以发现多了一个 C C C 的代价。

  • 否则,若存在其他不是环的基环树,那么可以等其执行完后对于其一个叶子当做条边辅助操作,也是多一个 C C C 的代价。

而如果都是环或者自环,无解。

接下来考虑在这个贪心的基础上 d p dp dp

可以发现在 d p dp dp 中,若产生了和贪心方案中不同的环,那么把某一个出边改变的点的出边改为贪心的,那么因为边时贪心选的,所以一定不劣,同时还能破坏环。因此不可能产生新的环。

d p i , S , r dp_{i,S,r} dpi,S,r 表示考虑了前 i i i 个字符的出边,集合 S S S 的环已经被破坏, r r r 表示时候与原图一样的最小代价。

d p dp dp 可以直接求,求答案的时候,我们对于没被破坏的环,我们需要额外支付 C C C 的代价,且若原图无解,则要求该方案不等于原图。

因为最多有 13 13 13 个环,所以复杂度为 Θ ( n + 26 × 2 13 ) \Theta(n+26\times 2^{13}) Θ(n+26×213)

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+7;
const int D = 27;
int dp[D][1<<(D>>1)][2];
int n,m;
char s[N],t[N];
int cost[D][D];
int p[D];
int C=0;
int bel[D];
int deg[N];
queue<int> q;
void findcir()
{
	for(int i=1;i<=26;i++)
	if(!deg[i])q.push(i);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		int y=p[x];
		deg[y]--;
		if(!deg[y])q.push(y);
	}
	for(int i=1;i<=26;i++)
	if(deg[i]&&p[i]!=i&&!bel[i])
	{
		++C;
		int x=i;
		while(!bel[x])
		{
			bel[x]=C;
			x=p[x];
		}
	}
}
int popcnt[1<<(D>>1)];
void ckmin(int &x,int v)
{
	x=min(x,v);
}
int main()
{
	cin>>n>>m;
	scanf("%s",s+1);
	scanf("%s",t+1);
	for(int i=1;i<=n;i++)
	{
		for(int c=1;c<=26;c++)cost[s[i]-'a'+1][c]++;
		cost[s[i]-'a'+1][t[i]-'a'+1]--;
	}
	for(int i=1;i<=26;i++)
	for(int j=1;j<=26;j++)
	if(i!=j) cost[i][j]+=m;
	for(int i=1;i<=26;i++)
	{
		p[i]=i;
		for(int j=1;j<=26;j++)
		if(cost[i][j]<cost[i][p[i]])p[i]=j;
	}
	for(int i=1;i<=26;i++)deg[p[i]]++;
	int flag=1;
	for(int i=1;i<=26;i++)if(deg[i]!=1)flag=0;
	findcir();
	if(!C)
	{
		int ans=0;
		for(int i=1;i<=26;i++)
		ans+=cost[i][p[i]];
		cout<<ans;
		return 0;
	}
	for(int i=0;i<=26;i++)
	for(int j=0;j<(1<<C);j++)
	for(int c=0;c<=1;c++)
	dp[i][j][c]=1e9;
	dp[0][0][0]=0;
	for(int i=1;i<(1<<C);i++)popcnt[i]=popcnt[i>>1]+(i&1);
	for(int i=1;i<=26;i++)
	for(int S=0;S<(1<<C);S++)
	for(int r=0;r<=1;r++)
	if(dp[i-1][S][r]!=1e9)
	for(int k=1;k<=26;k++)
	{
		int U=S;
		if(bel[i]&&p[i]!=k)U|=(1<<(bel[i]-1));
		if(bel[k]&&(p[i]!=k||bel[i]!=bel[k]))U|=(1<<(bel[k]-1));
		ckmin(dp[i][U][r|(p[i]!=k)],dp[i-1][S][r]+cost[i][k]);
	}
	int ans=1e9;
	for(int S=0;S<(1<<C);S++)
	for(int r=flag;r<=1;r++)
	ans=min(ans,dp[26][S][r]+(C-popcnt[S])*m);
	cout<<ans;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值