说在前面
被此题折磨致死….
题目
题面
给定两个长度为n的等长的小写字母串 A A 和 ,你可以做以下两种操作:
- 把 A A 中某个位置上的字符修改成另一个字符,用时 秒。比如:ababc变成ababa
- 把 A A 中某种字符全部修改成另一个种字符,用时 秒。比如:ababc变成acacc
同一时间只能做一个操作,求把
A
A
变成 的最小总耗时
范围:
len≤106
l
e
n
≤
10
6
输入输出格式
输入格式:
第一行一个整数N,表示字符串长度
接下来两行,每行一个字符串,表示
A
A
和
输出格式:
输出一个整数,表示答案
解法
(这题me只想到了前一半,后一半去看的题解…)
首先可以发现,一个位置的字符最多经过两次修改,先被
2
2
操作改一次,再由 操作改一次
另外,肯定不会出现先操作一次,再由
2
2
操作 统一改到目标字符 这种中转的情况,因为直接改到目标字符显然不会更差
所以,最优的方式一定是先经过一系列的 操作,然后由
1
1
微调
那些没有经过 操作的字符,可以看作是改成了自己而不付出代价
这样的话,对于某一种字符,我们肯定想让
2
2
操作的收获尽量的大
比如一共有 个
a
a
,其中目标为 的有
5
5
个,为的只有一个。那显然我们更希望进行
a→b
a
→
b
的
2
2
操作
如果全都这样贪心的选,最后形成了一个DAG或者环套树,那么显然方案是可以实现的,此时已经可以得出答案
- 对于DAG,我们只需要从拓扑序最低的开始即可
- 对于环套树,举个例子:,
b→a
b
→
a
,
c→a
c
→
a
,那么我们的操作顺序可以是:
b
b
变到 ,
a
a
变到 ,
c
c
变到
然而如果出现了环,我们就需要用另外一个字符中转,从而多消耗 C C 的代价,举个例子:, b→a b → a ,马,俄我们的操作顺序可以是: b b 变到 , a a 变到 , c c 变到 (其中 c c 是空的,不然这个中转无法完成)
这样的话,我们原来的贪心策略可能并不是最优秀的。那我们考虑一下,什么样的新策略,比现在的贪心策略优秀。显然,只有当新策略里没有环时,才可能比老策略优秀(如果都有环,那都要多付出 的代价,而原来的策略是贪心的,所以原来的要优一些)因此,只要采用新策略,新策略必须无环,才可能替代老策略
所以可以用dp来处理现在这个问题,枚举每一种字母的策略,然后转移。由于不可能生成新的环,所以转移的过程中只需要记录当前哪些环已经被破坏了(只要环上的策略变化,或者形成环套树,那么这个环就被破坏了),就可以得出答案但是还要注意一个问题,如果初始的贪心策略里,所有字母都在某个环中(自环除外),是不可能中转的。也就是说必须有新策略,所以dp时还需要另开一维来表示,决策是否变更过
也就是 dp[i][state][0/1] d p [ i ] [ s t a t e ] [ 0 / 1 ] 表示,处理了几个字符,消除环的情况,以及决策是否变更过
预处理字符间变换的花费即可快速转移。这个花费就是 2 2 操作所用的时间 加上 变换之后还得靠 操作来微调的时间
下面是自带大长度的代码
#include <cstdio> #include <cstring> #include <algorithm> using namespace std ; char aa[1000005] , bb[1000005] ; int N , C , cost[30][30] , g[30] , id[30] , id_c , cnt[30][30] ; bool exist[30] , flag , vis[30] , visr[30] ; int Fstate , dp[27][1<<13][2] , ans ; struct Union_Set{ int fa[30] ; void init(){ for( int i = 1 ; i <= 26 ; i ++ ) fa[i] = i ; } int find( int x ){ if( fa[x] == x ) return x ; return fa[x] = find( fa[x] ) ; } void Union( int a , int b ){ int Fa = find( a ) , Fb = find( b ) ; if( Fa != Fb ) fa[Fa] = Fb ; } } U ; void preWork(){ U.init() ; for( int i = 1 ; i <= N ; i ++ ) // 1 to 26 ; 'a' = 97 cnt[ aa[i]-96 ][ bb[i]-96 ] ++ , cnt[ aa[i]-96 ][0] ++ ; for( int i = 1 ; i <= 26 ; i ++ ){ int best = 1 ; if( cnt[i][0] ) exist[i] = true ; for( int j = 1 ; j <= 26 ; j ++ ){ cost[i][j] = ( i != j ) * C + cnt[i][0] - cnt[i][j] ; if( cost[i][best] > cost[i][j] ) best = j ; } g[i] = best ; U.Union( i , best ) ; } // get rings -------------------------------------------- for( int i = 1 , j , k ; i <= 26 ; i ++ ){ if( visr[ U.find(i) ] ) continue ; visr[ U.find(i) ] = true ; for( j = i ; !vis[j] ; j = g[j] ) vis[j] = true ; if( j == g[j] ) continue ; id[j] = ++id_c ; for( k = g[j] ; k != j ; k = g[k] ) id[k] = id_c ; } if( !id_c ){ // ring do not exist , ans equals to sum_cost for( int i = 1 ; i <= 26 ; i ++ ) ans += cost[i][ g[i] ] ; printf( "%d" , ans ) ; exit( 0 ) ; } Fstate = ( 1 << id_c ) - 1 ; // flag = true ; memset( vis , 0 , sizeof( vis ) ) ; for( int i = 1 ; i <= 26 && flag ; i ++ ){ if( !exist[i] || vis[ g[i] ] ) flag = false ; vis[ g[i] ] = true ; } } void UPD( int &x , int y ){ if( x > y ) x = y ; } void solve(){ memset( dp , 0x3f , sizeof( dp ) ) ; ans = 0x3f3f3f3f ; dp[0][0][0] = 0 ; // dp[last_char][ring_state][diff from origin] for( int i = 1 ; i <= 26 ; i ++ ){ for( int j = 0 ; j <= Fstate ; j ++ ){ for( int k = 1 ; k <= 26 ; k ++ ){ int nj = j ; if( id[i] && g[i] != k ) nj |= ( 1 << ( id[i] - 1 ) ) ; if( id[k] && ( g[i] != k || id[k] != id[i] ) ) nj |= ( 1 << ( id[k] - 1 ) ) ; UPD( dp[i][nj][ g[i]!=k ] , dp[i-1][j][0] + cost[i][k] ) ; UPD( dp[i][nj][1] , dp[i-1][j][1] + cost[i][k] ) ; } } } for( int j = 0 ; j <= Fstate ; j ++ ) UPD( ans , dp[26][j][1] + ( id_c - __builtin_popcount( j ) ) * C ) ; if( flag ) printf( "%d" , ans ) , exit( 0 ) ; for( int j = 0 ; j <= Fstate ; j ++ ) UPD( ans , dp[26][j][0] + ( id_c - __builtin_popcount( j ) ) * C ) ; printf( "%d" , ans ) ; } int main(){ scanf( "%d%d" , &N , &C ) ; scanf( "%s" , aa + 1 ) , scanf( "%s" , bb + 1 ) ; preWork() ; solve() ; }