版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
正题
考场上我只会枚举上下两层的正方形,然后暴力合并。
这题的题解思考方式很强:
首先正着反着插进去拍一遍序,去一下重。
考虑黑白染色,分成两组互不相关的点。
然后对于一个点,枚举附近三个点的方案数:表示有分别一条边连到a,b,c的可选点方案数,我们并不需要在乎当前点是什么。
上面这个东西可以用预处理出来。
枚举一组点,贡献的答案显然就是:
接着发现你被卡常了!
实际可以很容易的减少常数:
六组其实是相等的,所以我们可以保证来减少大约的预处理常数。
而枚举也是这样子的。保证,就会减少大约的求解常数,但是在求解过程中,实际上是可以调换的,而且可能存在重复,乘上一个系数就可以了。
#include<bits/stdc++.h>
using namespace std;
vector<string> V[11];
long long E[62][62],D[62][62][62],t[62];
string s;
int n;
const long long mod=998244353;
int chk(char x){
if(x>='a' && x<='z') return x-'a';
if(x>='A' && x<='Z') return x-'A'+26;
if(x>='0' && x<='9') return x-'0'+52;
}
int main(){
scanf("%d",&n);
int mmax=0;
for(int i=1;i<=n;i++){
cin>>s;
int len=s.length();
V[len].push_back(s);
for(int j=0;j<len/2;j++) swap(s[j],s[len-1-j]);
mmax=max(mmax,len);
V[len].push_back(s);
}
long long tot=0;
for(int i=3;i<=10;i++){
if(V[i].size()==0) continue;
sort(V[i].begin(),V[i].end());
memset(E,0,sizeof(E));
E[chk(V[i][0][0])][chk(V[i][0][i-1])]++;
for(int j=1;j<V[i].size();j++)
if(V[i][j]!=V[i][j-1]) E[chk(V[i][j][0])][chk(V[i][j][i-1])]++;
memset(D,0,sizeof(D));
for(int a=0;a<62;a++)
for(int b=0;b<62;b++) if(E[a][b])
for(int c=b;c<62;c++) if(E[a][c])
for(int d=c;d<62;d++) if(E[a][d])
D[b][c][d]+=1ll*E[a][b]*E[a][c]%mod*E[a][d]%mod,D[b][c][d]>=mod?D[b][c][d]-=mod:0;
int tmp=24;
long long ans=0;
for(int a=0;a<62;a++){
t[a]++;tmp/=t[a];
for(int b=a;b<62;b++){
t[b]++;tmp/=t[b];
for(int c=b;c<62;c++){
t[c]++;tmp/=t[c];
for(int d=c;d<62;d++){
t[d]++;tmp/=t[d];
ans+=1ll*tmp*D[a][b][c]%mod*D[a][b][d]%mod*D[a][c][d]%mod*D[b][c][d]%mod;
tmp*=t[d];t[d]--;
}
tmp*=t[c];t[c]--;
}
tmp*=t[b];t[b]--;
}
tmp*=t[a];t[a]--;
}
tot+=ans;
}
printf("%lld\n",tot%mod);
}