E - Level K Palindrome

爆零王發表於2021-05-05

題目大意:

As a token of his gratitude, Takahashi has decided to give Snuke a level-KK palindrome. A level-LL palindrome, where LL is a non-negative integer, is defined as follows:

  • Let rev(s)rev(s) denote the reversal of a string ss.
  • A string ss is said to be a palindrome when s=rev(s)s=rev(s).
  • The empty string and a string that is not a palindrome are level-00 palindromes.
  • For any non-empty level-(L1)(L−1) palindrome tt, the concatenation of t,rev(t)t,rev(t) in this order is a level-LL palindrome.
  • For any level-(L1)(L−1) palindrome tt and any character cc, the concatenation of t,c,rev(t)t,c,rev(t) in this order is a level-LL palindrome.

Now, Takahashi has a string SS. Determine whether it is possible to make SS an exactly level-KK palindrome by doing the following action zero or more times: choose a character in SS and change it to another lowercase English letter. If it is possible, find the minimum number of changes needed to make SS a level-KK palindrome.

自己翻譯去

題目思路:

粗略分析:

通過“最少改變”這個關鍵詞,我們不難想到貪心演算法。

對於本道題,我們的貪心策略是:
找到字母相同的位置,用桶統計這個集合中每個字母出現的次數。

找到出現最多和次多的字母及最多出現的個數,

並將集合中的字母全部變成出現最多的那個字母,

同時統計答案。

問題細化:

Q1:如何找到必須相同的字母的位置?

A:使用分治演算法,並用並查集來維護這個集合(並查集在替換操作上還有用處)

Q2:impossible情況

①k太大(大概是k>=20)

②len=pow(2,k) 

③len<pow(2,k-1)

Q3:特殊判斷!

1)替換後的字串有更深層的迴文。

這個時候記錄下的次多的字母個數就有用處了。只需要掉一個非後迴文串中心的字母就可以了。(要比較找最優方法)

並查集的性質也可以保證每個集合的總祖先是連續的處於字串的頭部的。非常方便進行替換操作。

2)特判:長度唯一的字串迴文深度為1

code:

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int k;
string s;
int ans=0;
int b[N][30];//
int maxn[N];//最大
int emaxn[N];//次大
int bel[N];//最小的子字串中的字母
int f[N];//並查集
vector<int> res;//存最小子字串的下標

int find(int x)//找祖先
{
    if(f[x]==x) return x;
    return f[x]=find(f[x]);
}

void h(int x,int y)//認祖先
{
    f[find(x)]=find(y);
    return ;
} 

void dfs(int l,int r,int lev){//二分
    if(lev==k) return ;
    int len=(r-l+1);
    if(len%2==1){
        dfs(l,l+len/2-1,lev+1);dfs(l+len/2+1,r,lev+1);
    }
    if(len%2==0){
        dfs(l,l+len/2-1,lev+1);dfs(l+len/2,r,lev+1);
    }int mid=(l+r)/2;//奇迴文串和偶迴文串需要分開討論
    for(int i=0;r-i>=mid;i++) h(r-i,l+i);//建立集合
    return ;
}

bool judge(int len){//判斷是否為迴文串
    for(int i=1;i<=len;i++) if(bel[i]!=bel[len-i+1]){return true;}
    return false;
}

int main(){
    cin>>k>>s;
    int len=s.size();
    s=" "+s;
    if(k>=20||len/(1<<k)==1||len<(1<<(k-1))){puts("impossible");return 0;}//無解的判斷
    
    for(int i=1;i<=len;i++) f[i]=i;//並查集初始化
    
    int dip=k;
    int anslen=len;//anslen表示最小子串的長度
    
    while(dip--){
        anslen/=2;//計算最小子串的長度
    }
    
    dfs(1,len,0);//二分
    for(int i=1;i<=len;i++){
        b[find(i)][s[i]-'a']++; 
        if(find(i)==i) res.push_back(i);//res存的是一個完整的最小子串
    }
    
    int sum=res.size();
    
    for(int i=0;i<sum;i++){
        int x=res[i];
        for(int j=0;j<26;j++){
            if(b[x][j]>maxn[x]) {emaxn[x]=maxn[x];maxn[x]=b[x][j];bel[x]=j;}
            else emaxn[x]=max(emaxn[x],b[x][j]);//找最大和找次大
        }
    }
    
    for(int i=0;i<sum;i++) ans+=maxn[res[i]];//統計ans
    
    if(!anslen){cout<<len-ans;return 0;}//最小子串長度為1的特判
    if(!judge(anslen)){//包含更深的迴文
        int minn=12345678;
        for(int i=1;i<=anslen;i++){
            if(anslen%2==1&&i==anslen/2+1) continue;
            minn=min(minn,maxn[i]-emaxn[i]);//尋找最佳替換方案
        }
        ans-=minn;
    }
    cout<<len-ans;
    return 0;
}

 

 

 

 

 

 

相關文章