BZOJ4406 WC2016 论战捆竹竿

Problem

BZOJ

Solution

显然是一个同余系最短路问题,转移方案就是所有|S|-border的长度,有 \(O(n)\) 种,暴力跑dijkstra的复杂度为 \(O(n^2\log n)\)

有一个结论,一个字符串的border的长度可以被分为 \(\log |S|\) 个等差数列。

那么我们不如来考虑一个等差数列所造成的影响,设等差数列共有 \(m\) 项,初值为 \(x\) ,那么它可以被表示为 \(\{ kd+x,k\in[0,m)\}\)

\[f[i]=\min_{j=0}^{m-1}(f[(i-x-jd)\bmod n]+x+jd)\]

如果我们能建立一个 \(\bmod x\) 的剩余系,那么接下来就相当于添加了一个 \(+d\) 的转移方案,但是这个转移有距离的限制。注意到能相互更新的状态会形成 \(\gcd(x,d)\) 个环,可以套路地拆下来并倍长,用单调队列即可解决距离的限制,时间复杂度为 \(O(x)\)

那么就只需要解决两个剩余系相互转化的问题了。其实很简单,设旧表的模数为 \(p\),新表的模数为 \(q\),只需要把旧表中每一列的最小能表达的值作为初始值放入新表中,然后只需要把转移方案 \(+p\) 加入表中,更新一次即可,这样的复杂度是 \(O(q)\) 的。

总的时间复杂度为 \(O(\sum n\log n)\)

注意把用来存border的数组,把a[len+1]的值清为0,这样才不会导致判的等差数列。

Code

#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=500010;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    if(f) x=-x;
}
int z,n,k,tot,top,now,nxt[maxn],a[maxn],stk[maxn<<1];
int l,r,q[maxn<<1];
ll w,ans,f[maxn],g[maxn];
char s[maxn];
int gcd(int a,int b){return b?gcd(b,a%b):a;}
void trans(int x)
{
    int D=gcd(x,now);
    memset(g,0x3f,x<<3);
    for(int i=0;i<now;i++) getmin(g[f[i]%x],f[i]);
    for(int i=0;i<D;i++)
    {
        top=0;
        for(int j=i;top==0||j!=i;j=(j+now)%x) stk[++top]=j;
        for(int j=1;j<=top;j++) stk[j+top]=stk[j];
        top<<=1;
        for(int j=2;j<=top;j++)
          getmin(g[stk[j]],g[stk[j-1]]+now);
    }
    memmove(f,g,x<<3);
    now=x;
}
void solve(int x,int d,int m)
{
    trans(x);
    if(d<0) return ;
    int D=gcd(x,d);
    for(int i=0;i<D;i++)
    {
        top=0;l=1;r=0;
        for(int j=i;top==0||j!=i;j=(j+d)%x) stk[++top]=j;
        for(int j=1;j<=top;j++) stk[j+top]=stk[j];
        top<<=1;
        for(int j=1;j<=top;j++)
        {
            while(l<=r&&j-q[l]>=m) ++l;
            if(l<=r) getmin(f[stk[j]],f[stk[q[l]]]+(j-q[l])*d+x);
            while(l<=r&&f[stk[q[r]]]+(j-q[r])*d>f[stk[j]]) --r;
            q[++r]=j;
        }
    }
}
int main()
{
    read(z);
    while(z--)
    {
        read(n);read(w);scanf("%s",s);
        ans=tot=k=0;memset(f,0x3f,n<<3);
        for(int i=1;i<n;i++)
        {
            while(k&&s[k]!=s[i]) k=nxt[k];
            if(s[k]==s[i]) nxt[i+1]=++k;
            else nxt[i+1]=0;
        }
        for(int i=nxt[n];i;i=nxt[i]) a[++tot]=n-i;
        now=n;f[0]=n;a[tot+1]=0;
        for(int i=1,j=1;i<=tot;i=j)
        {
            while(a[j+1]-a[j]==a[i+1]-a[i]) j++;
            solve(a[i],a[i+1]-a[i],j-i);
        }
        for(int i=0;i<now;i++) if(f[i]<=w) ans+=(w-f[i])/now+1;
        printf("%lld\n",ans);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/totorato/p/10904573.html
今日推荐