BZOJ5443:[CEOI2018]Lottery
我对状态空间的理解:https://www.cnblogs.com/AKMer/p/9622590.html
题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=5443
这题能在暴力美学分组中占压轴地位,是不简单的。我从未见过如此灵性的暴力题目(也许是我太弱了题目写少了)。
我们令\(ans[j][i]\)表示与串\([i,i+l-1]\)距离为\(j\)的串的个数,最暴力的方法显然是直接\(O(n^2*l)\)去匹配的。
但是其中我们会对一些字符进行若干次重复的比较。比如\([1,3]\)和\([2,4]\)匹配时以及\([2,4]\)和\([3,5]\)都会判断\(a[3]\)是否等于\(a[3]\)。很显然这种冗余的操作是不优的。那么我们怎么减少这样的操作呢?
性质:如果我们已经知道了\([l1,r1]\)与\([l2,r2]\)的距离,那么我们可以在\(O(1)\)时间内算出\([l1+1,r1+1]\)与\([l2+1,r2+1]\)的距离。设前者为\(s\),后者为\(t\),那么\(t=s-(a[l1]!=a[l2])+(a[r1+1]!=a[r2+1])\)。
也就是说,我们可以在\(O(n)\)的时间内匹配完\(n\)条子串。
我们枚举一个\(len\),令\(h1\)为\(1\),\(h2\)为\(h1+len\),然后暴力匹配\([h1,h1+l-1]\)和\([h2,h2+l-1]\),再在\(O(n)\)的时间内把所以起点下标差为\(len\)的所以串对匹配完并且统计答案。
枚举\(len\)的复杂度乘以匹配的复杂度为\(O(n^2)\),我们成功消去了一个\(l\)!
但是这题还卡空间……所以\(ans\)数组开不了那么大。
可是出题人关上了一扇门,却为我们留了一扇窗。
\(query\)的次数很小。我们可以将\([1,l]\)之内的所有数字都找到离自己最近的大于等于自己的\(k\),用一个\(pos[i]\)记录这个\(k\)。然后每次匹配出来距离为\(dis\),我们就把答案累计到\(ans[pos[dis]][i]\)里去,最后前缀和一下,就可以求出所有的\(query\)了。
时间复杂度:\(O(n^2)\)
空间复杂度:\(O(nq)\)
代码如下:
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e4+5;
bool v[maxn];
int n,m,l,cnt;
int ans[101][maxn];
int a[maxn],k[101],pos[maxn];
int read() {
int x=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
return x*f;
}
void solve() {
for(int len=1;len<=n-l;len++) {//枚举len
int res=0,h1=1,h2=h1+len;//res记录当前两个串的距离,h1是第一个串的起点,h2是第二个串的起点
for(int i=0;i<l;i++)
if(a[h1+i]!=a[h2+i])res++;
ans[pos[res]][h1]++;
ans[pos[res]][h2]++;//答案累计到ans[pos[res]][h1]和ans[pos[res]][h2]里
while(1) {
if(h2+l>n)break;//如果h2往后没有l那么长了就break
res-=(a[h1]!=a[h2]);
res+=(a[h1+l]!=a[h2+l]);//O(1)转移
h1++;h2++;
ans[pos[res]][h1]++;
ans[pos[res]][h2]++;//累计答案
}
}
}
int main() {
n=read();l=read();
for(int i=1;i<=n;i++)
a[i]=read();
m=read();pos[1]=1;//pos[1]等于1
for(int i=1;i<=m;i++)
k[i]=read(),v[k[i]]=1;//我这里用差分的方法求每个数的pos
for(int i=2;i<=l;i++)
pos[i]=pos[i-1]+v[i-1];//如果是在(k[i],k[i+1]]之间的数,pos[i]就是i+1
solve();
for(int i=1;i<=n-l+1;i++)
for(int j=1;j<=m;j++)
ans[j][i]+=ans[j-1][i];//前缀和统计答案
for(int i=1;i<=m;i++) {
for(int j=1;j<=n-l+1;j++)
printf("%d ",ans[pos[k[i]]][j]);//离线输出答案
puts("");
}
return 0;
}