CF718E Matvey's Birthday

一、题目

点此看题

二、解法

我自己的想法是把问题转化成 \(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);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15808925.html