一、题目
二、解法
我自己的想法是把问题转化成 \(8\) 个点 \(n\) 条边的问题(把每个颜色看成一个点),这样看似简单实则难做,因为问题的关键是求最远点对数量,所以计数应产生在点之间而不是在颜色之间(而且这道题并不好把颜色转化到点),但是上面的思考也不是全无作用,它告诉我们答案一定不超过 \(15\)
那么考虑怎么描述两点之间的最短距离,这里我们可以以颜色为中介,首先预处理出 \(f[i][j]\) 表示点 \(i\) 到颜色 \(j\) 的距离,\(g[i][j]\) 表示颜色 \(i\) 到颜色 \(j\) 的距离,这可以从每个颜色为起点 \(\tt bfs\) 解决。然后考虑点对 \((i,j)\) 之间的大致走法,我们可以花 \(|i-j|\) 直接走,或者是枚举一个会走字符边的颜色 \(k\) 计算长度:
\[\min(i-j,f[i][k]+f[j][k]+1)
\]
优化考虑去掉这个 \(\min\),因为答案不超过 \(15\) 所以我们暴力计算 \(i-j\leq 15\) 的这些点,其他点的答案一定是 \(\min(f[i][k]+f[j][k]+1)\),发现重要的是 \(f[j][k]\) 而不是 \(j\),所以我们给 \(j\) 划分等价类,因为 \(g[s_j][k]\leq f[j][k]\leq g[s_j][k]+1\),所以可以把 \(j\) 表示成一个颜色和 \(8\) 位二进制数的一个二元组,每一组的距离一定是相同的,那么时间复杂度 \(O(n\cdot 2^8\cdot 8^2)\)
三、总结
一定要注意观察答案的形式来判断思路是否可行。
当某一个东西范围很小的时候,可以考虑划分等价类来优化复杂度。
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
using namespace std;
const int M = 100005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,ans,c[M],f[M][8],g[8][8],o[8][256],vis[M],vc[8];
vector<int> v[M];char s[M];queue<int> q;long long cnt;
void upd(int w,int c)
{
if(w>ans) ans=w,cnt=c;
else if(w==ans) cnt+=c;
}
signed main()
{
n=read();scanf("%s",s+1);
for(int i=1;i<=n;i++)
c[i]=s[i]-'a',v[c[i]].push_back(i);
memset(f,0x3f,sizeof f);
memset(g,0x3f,sizeof g);
for(int i=0;i<8;i++)
{
memset(vis,0,sizeof vis);
memset(vc,0,sizeof vc);
for(int j:v[i])
f[j][i]=0,vis[j]=1,q.push(j);
g[i][i]=0;vc[i]=1;
while(!q.empty())
{
int u=q.front();q.pop();
if(u>1 && !vis[u-1])
vis[u-1]=1,f[u-1][i]=f[u][i]+1,q.push(u-1);
if(u<n && !vis[u+1])
vis[u+1]=1,f[u+1][i]=f[u][i]+1,q.push(u+1);
if(!vc[c[u]])
{
vc[c[u]]=1;g[c[u]][i]=f[u][i];
for(int x:v[c[u]]) if(!vis[x])
vis[x]=1,f[x][i]=f[u][i]+1,q.push(x);
}
}
}
for(int i=1;i<=n;i++)
for(int j=max(1,i-15);j<i;j++)
{
int d=i-j;
for(int k=0;k<8;k++)
d=min(d,f[i][k]+f[j][k]+1);
upd(d,1);
}
for(int i=17;i<=n;i++)
{
int s=0;
for(int j=0;j<8;j++)
s|=(f[i-16][j]-g[c[i-16]][j])<<j;
o[c[i-16]][s]++;
for(int j=0;j<8;j++)
for(int s=0;s<256;s++)
{
if(!o[j][s]) continue;
int d=15;
for(int k=0;k<8;k++)
d=min(d,f[i][k]+g[j][k]+(s>>k&1)+1);
upd(d,o[j][s]);
}
}
printf("%d %lld\n",ans,cnt);
}