[BZOJ4607]-[PA2015 Final]Edycja-性质+dp

说在前面

被此题折磨致死….


题目

BZOJ4607传送门

题面

给定两个长度为n的等长的小写字母串 A A B,你可以做以下两种操作:

  1. A A 中某个位置上的字符修改成另一个字符,用时 1 秒。比如:ababc变成ababa
  2. A A 中某种字符全部修改成另一个种字符,用时 c 秒。比如:ababc变成acacc

同一时间只能做一个操作,求把 A A 变成 B 的最小总耗时
范围: len106 l e n ≤ 10 6

输入输出格式

输入格式:
第一行一个整数N,表示字符串长度
接下来两行,每行一个字符串,表示 A A B

输出格式:
输出一个整数,表示答案


解法

(这题me只想到了前一半,后一半去看的题解…)
首先可以发现,一个位置的字符最多经过两次修改,先被 2 2 操作改一次,再由 1 操作改一次
另外,肯定不会出现先操作一次,再由 2 2 操作 统一改到目标字符 这种中转的情况,因为直接改到目标字符显然不会更差

所以,最优的方式一定是先经过一系列的 2 操作,然后由 1 1 微调
那些没有经过 2 操作的字符,可以看作是改成了自己而不付出代价

这样的话,对于某一种字符,我们肯定想让 2 2 操作的收获尽量的大
比如一共有 6 a a ,其中目标为 b 的有 5 5 个,为c的只有一个。那显然我们更希望进行 ab a → b 2 2 操作

如果全都这样贪心的选,最后形成了一个DAG或者环套树,那么显然方案是可以实现的,此时已经可以得出答案

  • 对于DAG,我们只需要从拓扑序最低的开始即可
  • 对于环套树,举个例子:ab ba b → a ca c → a ,那么我们的操作顺序可以是: b b 变到 c a a 变到 b c c 变到 a

    然而如果出现了环,我们就需要用另外一个字符中转,从而多消耗 C C 的代价,举个例子:ab ba b → a ,马,俄我们的操作顺序可以是: b b 变到 c a a 变到 b c c 变到 a(其中 c c 是空的,不然这个中转无法完成)
    这样的话,我们原来的贪心策略可能并不是最优秀的。那我们考虑一下,什么样的新策略,比现在的贪心策略优秀。显然,只有当新策略里没有环时,才可能比老策略优秀(如果都有环,那都要多付出 C 的代价,而原来的策略是贪心的,所以原来的要优一些)

    因此,只要采用新策略,新策略必须无环,才可能替代老策略
    所以可以用dp来处理现在这个问题,枚举每一种字母的策略,然后转移。由于不可能生成新的环,所以转移的过程中只需要记录当前哪些环已经被破坏了(只要环上的策略变化,或者形成环套树,那么这个环就被破坏了),就可以得出答案

    但是还要注意一个问题,如果初始的贪心策略里,所有字母都在某个环中(自环除外),是不可能中转的。也就是说必须有新策略,所以dp时还需要另开一维来表示,决策是否变更过

    也就是 dp[i][state][0/1] d p [ i ] [ s t a t e ] [ 0 / 1 ] 表示,处理了几个字符,消除环的情况,以及决策是否变更过
    预处理字符间变换的花费即可快速转移。这个花费就是 2 2 操作所用的时间 加上 变换之后还得靠 1 操作来微调的时间


    下面是自带大长度的代码

    #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() ;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值