loj3053

引言

那就是希望。

即便需要取模,也是光明。

它还是来了。

这题我尝试写过一次,寄了。然后开摆了。

现在决定重新补一补这题。

敬请收看:myee 调长剖调到 CSP 还没有调出来的惨状!

欢迎来看我什么时候补掉。当然也可能鸽了。


思路

注意到是连通块信息计数。

注意到这个贡献不方便计算。

考虑点减边容斥

我们计算对每个点,其向儿子子树距离不超过 \(L,L-1\) 的连通块方案数,与向父亲子树距离不超过 \(L\) 的连通块方案数,即可计算出答案。

我们设其为 \(h_{p,x},g_{p,x}\)

则,我们有

\[h_{p,x}=\prod_s(h_{s,x-1}+1) \]

\[g_{p,x}=1+g_{f,x-1}\prod_{f_a=f,a\neq p}(h_{a,x-2}+1) \]

然后容易发现每个点、边的贡献均可以用 \(h_{p,L},h_{p,L-1},g_{p,L}\) 表出。

问题是怎么快速计算。


计算 \(h\)

注意到 \(x\) 是一个和深度相关的量,于是考虑长链剖分。

现在要支持全局加、前缀单点乘、后缀乘操作。

(注意,\(h\) 是无限长的,因此长剖信息会出现一段后缀乘)

考虑直接维护这种玩意儿。

一种方法是线段树,但显然常数忒大,跑不过去。

考虑维护全局乘法标记,然后前缀求逆暴力乘。

容易发现可能会除 \(0\)……然后就 GG 了……

考虑怎么特判后缀乘 \(0\) 的操作。

考虑打个标记,维护当前从哪一项开始全是 \(0\)

但是这个标记在全局加 \(1\) 后就会失效,考虑怎么办。

把标记改定义为:\(h_{p,\ge a}=C\),其中 \(a,C\) 被计入 \(tag\)

这样就可以兼容全局加了。

诶,我上次是不是这里就写成从某点开始均为 \(0\) 的标记了。


计算 \(g\)

首先,容易发现,我们对于每一个点,只用维护

\[g_{p,L-k},\forall k\in[0,dep_p] \]

即可得到所有答案中的 \(g_{p,L}\)

问题是怎么维护。

考虑先对根节点暴力计算 \(g\)

这个很简单。全部都是 \(1\)

然后下传信息。

先传轻儿子,再传重儿子。

对轻儿子,直接重新建一段信息链。

对重儿子,继承当前点信息再全局加 \(1\)

问题是,如何统计上 \(\prod_{f_a=f,a\neq p}(h_{a,x-2}+1)\) 的贡献?

一种想法是用除法,\(\prod_{f_a=f,a\neq p}(h_{a,x-2}+1)=h_{f,x-1}/h_{p,x-2}\)

非常抱歉,然而这样会出现 \(0/0\),是未定式

我们不能指望通过统计区间有几个 \(0\) 使信息可减,因为长剖天然性质。

因此只能借助于维护前后缀积了。

前缀积,可以把前面计算 \(h\) 的过程可持久化,计算完某轻儿子的 \(g\) 之后把其对应 \(h\) 的贡献暴力加入 \(g\)

后缀积呢?

注意到我们计算 \(h_f\) 时,如果枚举儿子顺序恰好为当前计算 \(g_f\) 时枚举顺序的逆序,则其的每一个版本恰好就对应着一个 \(h_a+1\) 的后缀积。

对此,直接暴力更新儿子节点信息即可。

然后由于版本访问时间的特性,可以使用可回退化数据结构代替可持久化。


Code 实时记录

\(11\!:\!53\):准备开始写。

\(14\!:\!08\):写了个可回退化修改元素的 DS。

template<typename T> // 可回退化 DS
struct Roll{
    std::vector<T*>At;std::vector<T>W;uint tp;
    Roll():At(),W(),tp(0){}
    voi clear(){At.clear(),W.clear(),tp=0;}
    voi chg(T&p,T v){
        if(At.size()>tp)At.resize(tp),W.resize(tp);
        At.push_back(&p),W.push_back(p),p=v,tp++;
    }
    voi step_on(){if(tp<At.size())std::swap(*At[tp],W[tp]),tp++;}
    voi step_back(){if(tp)--tp,std::swap(*At[tp],W[tp]);}
    voi go_time(uint t){
        _min(t,(uint)At.size());
        while(tp<t)step_on();
        while(tp>t)step_back();
    }
};

\(14\!:\!53\):写了长剖的第一个 DS。

struct vec1{
    modint*P,mul,add,C;uint from,siz;Roll<uint>Ru;Roll<modint>Rv;
    vec1():P(NULL),mul(1),add(),C(),from(0),siz(0),Ru(),Rv(){}
    voi build(uint len){P=NewMemory(len);}
    voi chg(uint&a,uint b){Ru.chg(a,b);}
    voi chg(modint&a,modint b){Rv.chg(a,b);}
    modint val(uint p){return from+p>=siz?C:P[siz-p-1]*mul+add;}
    voi push(){chg(P[siz],-add/mul),chg(C,C+1),chg(add,add+1),chg(siz,siz+1);}
    voi merge(vec1&p){
        while(from>siz-p.siz)chg(from,from-1),chg(P[from],(C-add)/mul);
        modint a=p.val(1e9);
        if(a.empty()){
            for(uint i=0;i<p.siz;i++)chg(P[siz-i-1],(val(i)*p.val(i)-add)/mul);
            chg(C,0),chg(from,siz-p.siz);
        }
        else{
            for(uint i=0;i<p.siz;i++)chg(P[siz-i-1],val(i)*p.val(i));
            chg(mul,mul*a),chg(add,add*a),chg(C,C*a);
            for(uint i=0;i<p.siz;i++)chg(P[siz-i-1],(P[siz-i-1]-add)/mul);
        }
    }
};

\(16\!:\!08\):调出了长剖的第一部分,然后加了点优化。

\(17\!:\!42\):(不加懒惰标记地)暴力实现了长剖的第二部分。

\(18\!:\!11\):发现可回退化内存池写挂了。(上面的代码已经修过了)改掉后 Luogu \(80\rm pts\),loj \(76\rm pts\)

\(18\!:\!50\):不想在 \(h\) 的计算中写 \(g\) 的那个优化了,直接把第二部分中可能除零的懒标记用暴力修改代替,直接过了……甚至离线求逆元我都没有写……有没有神仙教教这么做的复杂度对不对啊?能不能卡啊?

唔 qoj 测了一下 T 掉了最后一个点……Luogu 上不开 O2 也过不去……似乎卡的是常数不是复杂性?所以这个东西到底卡不卡的掉啊?发现内存池写的不好,去改改。

\(19\!:\!05\):qoj 上通过了。

挂核心代码。

const ullt Mod=998244353;
typedef ConstMod::mod_ullt<Mod>modint;
typedef std::vector<modint>modvec;
template<typename T> // 可回退化 DS
struct Roll{
    std::vector<T*>At;std::vector<T>W;uint tp;
    Roll():At(),W(),tp(0){}
    voi chg(T&p,T v){At.push_back(&p),W.push_back(p),p=v,tp++;}
    voi revoke(uint t){while(tp>t)--tp,*At[tp]=W[tp];}
};
modint _Base[20000005],*End=_Base+20000000;
inline modint*NewMemory(uint n){return End-=n;}
struct vec1{
    modint*P,mul,add,inv,C;uint from,siz;Roll<uint>Ru;Roll<modint>Rv;
    vec1():P(NULL),mul(1),add(),inv(1),C(),from(0),siz(0),Ru(),Rv(){}
    voi build(uint len){P=NewMemory(len);}
    voi chg(uint&a,uint b){Ru.chg(a,b);}
    voi chg(modint&a,modint b){Rv.chg(a,b);}
    modint val(uint p){return from+p>=siz?C:P[siz-p-1]*mul+add;}
    voi push(){chg(P[siz],-add*inv),chg(C,C+1),chg(add,add+1),chg(siz,siz+1);}
    voi merge(vec1&p){
        while(from>siz-p.siz)chg(from,from-1),chg(P[from],(C-add)*inv);
        modint a=p.val(1e9);
        if(a.empty()){
            for(uint i=0;i<p.siz;i++)chg(P[siz-i-1],(val(i)*p.val(i)-add)*inv);
            chg(C,0),chg(from,siz-p.siz);
        }
        else{
            for(uint i=0;i<p.siz;i++)chg(P[siz-i-1],val(i)*p.val(i));
            chg(mul,mul*a),chg(add,add*a),chg(C,C*a),chg(inv,inv/a);
            for(uint i=0;i<p.siz;i++)chg(P[siz-i-1],(P[siz-i-1]-add)*inv);
        }
    }
};
std::vector<uint>Way[1000005],Son[1000005];
uint Dep[1000005],MaxDep[1000005],Heavy[1000005],Fath[1000005],x;
voi dfs0(uint p,uint f){
    Heavy[p]=-1,MaxDep[p]=Dep[p],Fath[p]=f;
    for(auto s:Way[p])if(s!=f)Dep[s]=Dep[p]+1,dfs0(s,p),Son[p].push_back(s);
    std::sort(Son[p].begin(),Son[p].end(),[&](uint a,uint b){return MaxDep[a]>MaxDep[b];});
    if(Son[p].size())MaxDep[p]=MaxDep[Heavy[p]=Son[p][0]];
}
vec1 H[1000005];uint At[1000005];
modint GetVal[3][1000005];
uint Time[2][1000005];
voi dfs1(uint p,uint r){
    if(~Heavy[p])dfs1(Heavy[p],r),At[p]=At[Heavy[p]];else H[At[p]=p].build(Dep[p]-Dep[r]+2);
    vec1&h=H[At[p]];h.push();
    for(auto s:Son[p])if(s!=Heavy[p])
        dfs1(s,s),Time[0][s]=h.Ru.tp,Time[1][s]=h.Rv.tp,H[At[s]].push(),h.merge(H[At[s]]);
    GetVal[0][p]=h.val(x);if(x)GetVal[1][p]=h.val(x-1);
}
struct vec2{
    modint*P;modint add,mul,inv;uint siz;
    vec2():P(NULL),add(),mul(1),inv(1),siz(){}
    voi build(uint len){P=NewMemory(siz=len);}
    modint val(uint p){return P[siz-p-1]*mul+add;}
    voi pop(){siz--;}
};
vec2 G[1000005];uint At2[1000005];
voi dfs2(uint p){
    vec1&h=H[At[p]];vec2&g=G[At2[p]];
    if(!~Fath[p])g.build(MaxDep[p]+1),g.add=1;
    GetVal[2][p]=g.val(0),g.pop();
    if(!~Heavy[p])return;
    std::reverse(Son[p].begin(),Son[p].end());
    for(auto s:Son[p])if(s!=Heavy[p]){
        h.Ru.revoke(Time[0][s]),h.Rv.revoke(Time[1][s]);
        vec2&gs=G[At2[s]=s];
        gs.build(MaxDep[s]-Dep[s]+1),gs.add=1;
        for(uint i=0;i<gs.siz&&i<x;i++)
            gs.P[gs.siz-i-1]=g.val(i)*h.val(x-i-1);
        vec1&hs=H[At[s]];
        if(hs.siz>=std::min(g.siz,x)){
            for(uint i=0;i<g.siz&&i<x;i++)g.P[g.siz-i-1]=(hs.val(x-i-1)*g.val(i)-g.add)*g.inv;
        }else{
            if(hs.val(1e9).empty()){
                for(uint i=0;i<g.siz&&i<x;i++)g.P[g.siz-i-1]=(hs.val(x-i-1)*g.val(i)-g.add)*g.inv;
            }
            else{
                for(uint i=std::min(x,g.siz)-hs.siz;i<std::min(x,g.siz);i++)
                    g.P[g.siz-i-1]=hs.val(x-i-1)*g.val(i);
                g.mul*=hs.val(1e9),g.add*=hs.val(1e9),g.inv/=hs.val(1e9);
                for(uint i=std::min(x,g.siz)-hs.siz;i<std::min(x,g.siz);i++)
                    g.P[g.siz-i-1]=(g.P[g.siz-i-1]-g.add)*g.inv;
            }
        }
        dfs2(s);
    }
    if(g.siz>x)g.P[g.siz-x-1]=-g.add*g.inv;
    G[At2[Heavy[p]]=At2[p]].add++,dfs2(Heavy[p]);
}
int main()
{
#ifdef MYEE
    freopen("QAQ.in","r",stdin);
#endif
    uint n,k;scanf("%u%u%u",&n,&x,&k);
    for(uint i=1,u,v;i<n;i++)scanf("%u%u",&u,&v),Way[--u].push_back(--v),Way[v].push_back(u);
    dfs0(0,-1),dfs1(0,0),dfs2(0);modint ans;
    for(uint i=0;i<n;i++)ans+=(GetVal[0][i]*GetVal[2][i])^k;
    for(uint i=1;i<n;i++)ans-=(GetVal[1][i]*(GetVal[2][i]-1))^k;
    ans.println();
    return 0;
}
posted @ 2022-10-24 09:33  myee  阅读(58)  评论(0编辑  收藏  举报