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;
}
posted @ 2018-09-13 17:27  AKMer  阅读(367)  评论(0编辑  收藏  举报