bzoj4607

  先分析可以得到先进行2操作再进行1操作不会更差。
  考虑设cost[i][j]表示原本为i的字母全部变成了j的代价,那么代价等于原本为i,后来不是j的字符个数,如果i不等于j,代价还要加上C。现在先考虑对每个i选择一条最短的出边(g[i])。然后现在构成了一张图。
  现在如果这张图中没有环,或者这张图全是大小为1的环,那么答案就是合法的,可以直接输出Σcost[i][g[i]]。(注意这里的环不包括环套树,有环不合法是因为一个环不可能交换成功,比如g[a]=b,g[b]=a,如果此时只有这两种颜色那么这辈子都换不过去。但是环套树不一样,读者可以自己画一下。)
  首先考虑如果所有的i,g[i]构成了一个排列(除了g[i]全部等于i的情况),可以发现整个图就是很多的环,那么一定不可能最后交换过去,所以排列时不合法的。
  但如果不是排列,意味着图中有着一个链或者环套树,那么再考虑g[a]=b,g[b]=a,c三种颜色的情况,可以多花C的代价先使b->c(此时原本的c是空的,因为一定存在一个g[c]=d使得c已经先换成了d。),a->b,c->b,那么交换成功。所以假设一张图不是排列的话,我们可以花C*环的个数的代价交换成功,但是因为对于每个环多花了C的代价,那么以前那样连边就不一定是最优的了,怎么办呢?
  我们假设可以改掉一些点的出边使得答案更优,首先看如果改掉出边后形成了新的大于等于2的环,那么环上肯定有点i现在连接的点和以前的g[i]不一样了,那么我们把i的连边改回g[i],可以发现首先边本身的代价变少了,其次这个新的环一定被破坏了,那么答案一定不会更劣(就算i改回g[i]后仍然形成了环也是如此。)所以改边之后一定存在一个最优解使得没有出现新的环。

  然后就可以考虑设计dp了,f[i][S][0/1]表示到了第i个点,原来环被破坏了的集合为S,是否和原图一样。(因为如果原图是一个排列,就只能用f[][][1]更新答案,因为必须和原图不一样)。因为考虑到如果原图是一个排列,改了边之后没有形成新的环,那么现在新图一定不是一个排列了,而如果原图不是一个排列,现在新图也一定不是一个排列。转移的时候考虑枚举当前点i匹配的是哪个点。一开始把所有的环套树加进来(环套树树上的部分不算在环中),考虑i原本在一个环里,k原本在另一个环里,这是把i的连边改成k,那么i和k的环同时被破坏了(因为k的环变成了环套树)。会发现dp时环套树树上的部分会自动把这颗环套树消掉。最后用f[26][T][0/1](注意是只能用1还是0、1都能用)+剩下环的个数*C。

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <ctime>
#define inf 1000000000
using namespace std;
int n,C,A[27][27],num[27],cost[27][27],g[27],d[27];
int col = 0,id[27],Ans = 2000000000,f[28][8300][2],father[27];
char s[1000010],c[1000010];
bool visit[27],flag = true,exist[27];
int count(int x) {
	int ret = 0;
	while(x > 0) 
	{
		ret ++;
		x -= (x & (-x));
	}
	return ret;
}
int getfather(int x) {
	if(father[x] == x) return x;
	father[x] = getfather(father[x]);
	return father[x];
}
int main() {
	scanf("%d%d",&n,&C);
	scanf("%s",s + 1);
	scanf("%s",c + 1);
	for(int i = 1;i <= n;i ++) 
		A[s[i] - 'a' + 1][c[i] - 'a' + 1] ++,num[s[i] - 'a' + 1] ++;
	for(int i = 1;i <= 26;i ++) for(int j = 1;j <= 26;j ++)
		cost[i][j] = num[i] - A[i][j] + C * (i != j);
	for(int i = 1;i <= 26;i ++) father[i] = i;
	for(int i = 1;i <= 26;i ++) {
		g[i] = 1;
		for(int j = 2;j <= 26;j ++) if(cost[i][j] < cost[i][g[i]])
			g[i] = j;
		d[g[i]] ++;if(getfather(i) != getfather(g[i])) father[father[i]] = father[g[i]];
	}
	memset(visit,false,sizeof(visit));
	memset(exist,true,sizeof(exist));
	for(int i = 1;i <= 26;i ++) if(exist[getfather(i)]) {
		exist[father[i]] = false;
		visit[i] = true;int j;
		for(j = g[i];visit[j] == false;j = g[j]) visit[j] = true;
		if(g[j] == j) continue;
		col = col + 1;id[j] = col;
		for(int k = g[j];k != j;k = g[k]) id[k] = col;
	}
	if(col == 0) {
		Ans = 0;
		for(int i = 1;i <= 26;i ++)
			Ans += cost[i][g[i]];
		printf("%d",Ans);
		return 0;
	}
	for(int i = 1;i <= 26;i ++) if(d[i] != 1) flag = false;
	for(int i = 0;i <= 26;i ++) for(int j = 0;j <= (1 << col) - 1;j ++)
	for(int k = 0;k <= 1;k ++) f[i][j][k] = inf;
	f[0][0][0] = 0;
	for(int i = 1;i <= 26;i ++) for(int j = 0;j <= (1 << col) - 1;j ++) 
	for(int k = 0;k <= 1;k ++) if(f[i - 1][j][k] < inf) for(int h = 1;h <= 26;h ++)
	{
		int J = j;
		if(g[i] != h && id[i] != 0) J |= (1 << (id[i] - 1));
		if(id[h] != 0 && (g[i] != h || id[h] != id[i])) J |= (1 << (id[h] - 1));
		f[i][J][(k || (h != g[i]))] = min(f[i][J][(k || (h != g[i]))],f[i - 1][j][k] + cost[i][h]);
	}
	if(flag == true) 
	{
		for(int j = 0;j <= (1 << col) - 1;j ++) for(int k = 1;k <= 1;k ++) 
			Ans = min(Ans,f[26][j][k] + (col - count(j)) * C);
		printf("%d",Ans);return 0;
	}
	for(int j = 0;j <= (1 << col) - 1;j ++) for(int k = 0;k <= 1;k ++) 
		Ans = min(Ans,f[26][j][k] + (col - count(j)) * C);
	printf("%d",Ans);
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值