【2019.1雅礼集训DAY2 T2】bracket(点分治+FFT)

题意

给定一棵有 n 个节点的无根树,每个节点上是一个字符,要么是(,要么是)。
定义 S(x, y) 为从 x 开始沿着最短路走到 y,将沿途经过的点上的字符依次连
起来得到的字符串。
合法括号序定义如下:
1,()是合法的。
2,若 A,合法,则(A)也合法。
3,若 A,B 分别合法,则 AB 也合法。
函数 f(x, y) 等于对 S(x, y) 进行划分,使得每一个部分都是合法括号序,能得
到的最大的段数,比如(())()()的最大段数为 3,(()())(())的最大段数为 2。
特别的,如果 S(x,y)本身并不是合法括号序,则 f(x,y)=0。
m 次询问,每次输入一个 k,查询有多少点对的 f 值为 k。

题解

点分治

这种跟路径有关的题,很容易想到点分治。
对每个重心,求出从其它结点走到重心的路径上,括号的前缀和sum,和这条路径已经包含了几段可以划分的段数cnt(cnt即为路径上有多少个结点前缀和等于挡墙结点的sum),为保证合法,此时的sum为最大值时算合法。即括号序列为()()(())(()((((这样(假设最后一个括号为重心,sum的意义是从右往左的前缀和);记录A[sum][cnt]表示这种状态的节点数。
同理,记录从重心走到其它结点上的状态,括号序列为)))()))()()()()(最左边的括号为重心),sum需保证为路径上的最小值(肯定非正数)才算合法。用B[sum][cnt]表示这种状态的节点数。
则答案为 a n s [ k ] + = ∑ i = 0 k A [ s u m ] [ i ] × B [ − s u m ] [ k − i − 1 ] ans[k]+=\sum_{i=0}^k A[sum][i]\times B[-sum][k-i-1] ans[k]+=i=0kA[sum][i]×B[sum][ki1]
直接计算会把从一个结点走向重心,再走回来这样的路径算上,最后再减去即可。

FFT优化

上面的式子满足卷积的形式,直接FFT就行了。

复杂度分析

这个算法看似是 O ( N 2 l o g 2 N ) O(N^2log_2N) O(N2log2N)的。
实际上点分治过程中,sum会不断减小,每次砍一半。
对于cnt,观察一串括号的cnt值

    )))()))()()()())))())((()()()()()()()())()
cnt:000010001020304000010000102030405060708001

cnt的总是分成很多段,每段的合法位置都是1个1个增加的。且每一段都对应的sum值都不同。
对于点分治的每一层,所有sum的最高cnt加起来与这一层结点数为同一个数量级。所有FFT的复杂度为当前这一层的节点数*log。
加上点分治,总复杂度为 O ( N l o g 2 2 N ) O(N{log_2}^2N) O(Nlog22N)

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN=50005;
const double PI=acos(-1);

struct cpx
{
    double r,i;
    cpx(){}
    cpx(double r,double i):r(r),i(i){}
    cpx operator + (const cpx &t)const
    {return cpx(r+t.r,i+t.i);}
    cpx operator - (const cpx &t)const
    {return cpx(r-t.r,i-t.i);}
    cpx operator * (const cpx &t)const
    {return cpx(r*t.r-i*t.i,r*t.i+i*t.r);}
    cpx operator / (double k)const
    {return cpx(r/k,i/k);}
};

void FFT(cpx A[],int n,int mode)
{
    for(int i=0,j=0;i<n;i++)
    {
        if(i<j)
            swap(A[i],A[j]);
        int k=n>>1;
        while(k&j)
            j^=k,k>>=1;
        j^=k;
    }
    for(int i=1;i<n;i<<=1)
    {
        cpx w1=cpx(cos(PI/i),sin(PI/i)*mode);
        for(int j=0;j<n;j+=(i<<1))
        {
            cpx w=cpx(1,0);
            for(int l=j,r=j+i;l<j+i;l++,r++,w=w*w1)
            {
                cpx tmp=A[r]*w;
                A[r]=A[l]-tmp;
                A[l]=A[l]+tmp;
            }
        }
    }
    if(mode==-1)
        for(int i=0;i<n;i++)
            A[i]=A[i]/n;
}

struct Edge
{
    int v;
    Edge *nxt;
    Edge(){}
    Edge(int _v,Edge *_n):v(_v),nxt(_n){}
};

struct Graph
{
    Edge edges[MAXN*2],*adj[MAXN],*ed_it;

    Graph(){ed_it=edges;}
    void AddEdge(int u,int v)
    {
        *ed_it=Edge(v,adj[u]);
        adj[u]=ed_it++;
    }
};

long long ans[MAXN];
int n,val[MAXN];
Graph G;

bool used[MAXN];
int siz[MAXN],mxdep[MAXN];
int stk[MAXN],tp;
vector<int> le[MAXN],ri[MAXN];

void dfs(int u,int fa=0)
{
    stk[++tp]=u;
    mxdep[u]=1;
    siz[u]=1;
    for(Edge *e=G.adj[u];e;e=e->nxt)
        if(!used[e->v]&&e->v!=fa)
        {
            dfs(e->v,u);
            mxdep[u]=max(mxdep[u],mxdep[e->v]+1);
            siz[u]+=siz[e->v];
        }
}
int FindCentroid(int u)
{
    int mn=0x3F3F3F3F,res=0;
    for(int i=1;i<=tp;i++)
    {
        int tmp=max(siz[stk[i]],siz[u]-siz[stk[i]]);
        if(mn>tmp)
            mn=tmp,res=stk[i];
    }
    return res;
}
void AddLeft(int u,int fa=0,int sum=0,int mx=0,int cnt=1)
{
    sum+=val[u];
    if(sum==mx)
        cnt++;
    if(sum>mx)
        mx=sum,cnt=1;
    if(sum==mx)
        le[sum].push_back(cnt-1+(sum!=0));
    for(Edge *e=G.adj[u];e;e=e->nxt)
        if(!used[e->v]&&e->v!=fa)
            AddLeft(e->v,u,sum,mx,cnt);
}
void AddRight(int u,int fa=0,int sum=0,int mn=0,int cnt=1)
{
    sum+=val[u];
    if(sum==mn)
        cnt++;
    if(sum<mn)
        mn=sum,cnt=1;
    if(sum==mn)
        ri[-sum].push_back(cnt-1);
    for(Edge *e=G.adj[u];e;e=e->nxt)
        if(!used[e->v]&&e->v!=fa)
            AddRight(e->v,u,sum,mn,cnt);
}
void UpdateAns(int flag,int mxsum)
{
    static cpx A[MAXN*2],B[MAXN*2];
    for(int s=0;s<=mxsum;s++)
    {
        int len1=1,len2=1,len=1;
        for(auto x:le[s])
            A[x].r++,len1=max(len1,x+1);
        le[s].clear();
        for(auto y:ri[s])
            B[y].r++,len2=max(len2,y+1);
        ri[s].clear();
        while(len<len1+len2-1)
            len<<=1;
        FFT(A,len,1);
        FFT(B,len,1);
        for(int i=0;i<len;i++)
            A[i]=A[i]*B[i];
        FFT(A,len,-1);
        for(int i=0;i<len1+len2-1;i++)
            ans[i]+=1LL*flag*(long long)floor(A[i].r+0.5);
        memset(A,0,sizeof(cpx)*len);
        memset(B,0,sizeof(cpx)*len);
    }
}
void CentroidDecomposition(int u)
{
    tp=0;
    dfs(u);
    int g=FindCentroid(u);

    for(Edge *e=G.adj[g];e;e=e->nxt)
        if(!used[e->v])
            AddLeft(e->v,g);
    le[0].push_back(0);
    AddRight(g);
    UpdateAns(1,mxdep[u]);

    for(Edge *e=G.adj[g];e;e=e->nxt)
        if(!used[e->v])
        {
            AddLeft(e->v,g);
            AddRight(e->v,g,val[g],min(val[g],0),1);
            UpdateAns(-1,mxdep[e->v]+1);
        }

    used[g]=true;
    for(Edge *e=G.adj[g];e;e=e->nxt)
        if(!used[e->v])
            CentroidDecomposition(e->v);
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        G.AddEdge(u,v);
        G.AddEdge(v,u);
    }
    for(int i=1;i<=n;i++)
    {
        char s[3];
        scanf("%s",s);
        val[i]=s[0]=='('?1:-1;
    }

    CentroidDecomposition(1);

    int m;
    scanf("%d",&m);
    while(m--)
    {
        int k;
        scanf("%d",&k);
        printf("%lld\n",ans[k]);
    }

    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值