E - Level K Palindrome
题目大意:
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-(L−1)(L−1) palindrome tt, the concatenation of t,rev(t)t,rev(t) in this order is a level-LL palindrome.
- For any level-(L−1)(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; }