CodeForces 1080E - Sonya and Matrix Beauty
洛谷题目页面传送门 & CodeForces题目页面传送门
若一个矩阵满足存在一种重新排列每行的方案,使得排列之后的每行每列都是回文串,那么称它是美丽的。给定一个
n\times m 的字符矩阵a ,求有多少个美丽子矩阵。
考虑探索一个矩阵美丽的充要条件。
某行能够重排之后变成回文串,显然当且仅当这行出现次数为奇数的字符的数量
接下来考虑每列都回文的充要条件。显然,重排每行之后能够每列都回文,当且仅当重排之后能够从上到下每行的字符串组成的字符串序列回文,显然又等价于从上到下每行的字符多重集组成的多重集序列回文。这个字符多重集可以用一个
这样一来,我们显然可以枚举子矩阵的起始列、终止列,就转化成了
然鹅这一次Manacher与正常的稍有不同。对于每一次枚举,因为有可能有的行不满足“这行出现次数为奇数的字符的数量
至于
时间复杂度
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=250,M=250,LET=26;
int n/*行数*/,m/*列数*/;
char a[N+1][M+5];//字符矩阵
vector<int> cnt[N+1];//|Σ|元组
int odd[N+1];//odd[i]表示cnt[i]中奇数的数量
int s;//|b|
vector<int> b[2*N+2];//塞上分隔符之后的|Σ|元组序列
bool ok[2*N+2];//ok[i]表示b[i]能否行内回文
void sep(){//塞分隔符(分隔符是vector<int>(LET,0))
b[s=1]=vector<int>(LET,0);ok[s]=true;//分隔符自然可匹配
for(int i=1;i<=n;i++)b[++s]=cnt[i],ok[s]=odd[i]<=1,b[++s]=vector<int>(LET,0),ok[s]=true;
}
bool eq(int x,int y){return ok[x]&&ok[y]&&b[x]==b[y];}//可匹配函数
int rds[2*N+2];//最长回文半径数组
void manacher(){//对b跑Manacher算法
int mid=0,r=0;
for(int i=1;i<=s;i++)
if(r<i){
rds[i]=0;
while(i-rds[i]>=1&&i+rds[i]<=s&&eq(i-rds[i],i+rds[i])/*调用可匹配函数*/)rds[i]++;
mid=i;r=i+rds[i]-1;
}
else if(i+rds[2*mid-i]<=r)rds[i]=rds[2*mid-i];
else{
rds[i]=r-i+1;
while(i-rds[i]>=1&&i+rds[i]<=s&&eq(i-rds[i],i+rds[i])/*调用可匹配函数*/)rds[i]++;
mid=i;r=i+rds[i]-1;
}
}
signed main(){
// freopen("C:\\Users\\chenx\\OneDrive\\桌面\\data.in","r",stdin);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i]+1;
for(int i=1;i<=n;i++)cnt[i].resize(LET);
int ans=0;
for(int i=1;i<=m;i++){//枚举起始列
for(int j=1;j<=n;j++){//新一轮递推,清空数据
for(int k=0;k<LET;k++)cnt[j][k]=0;
odd[j]=0;
}
for(int j=i;j<=m;j++){//枚举终止列
for(int k=1;k<=n;k++)odd[k]+=++cnt[k][a[k][j]-'a']&1?1:-1;//递推求odd,cnt
sep();//塞分隔符
manacher();//对b跑Manacher
for(int k=1;k<=2*n+1;k++)ans+=rds[k]>>1;//贡献答案
}
}
cout<<ans;
return 0;
}