zoukankan      html  css  js  c++  java
  • [九省联考2018]秘密袭击coat

    [九省联考2018]秘密袭击coat 

    研究半天题解啊。。。

    全网几乎唯一的官方做法的题解:链接

    别的都是暴力。。。。

    要是n=3333暴力就完了。

     

    一、问题转化

    每个联通块第k大的数,直观统计的话,会枚举每个点作为第k大,看看有多少个连通块。但是这样要点分治。而且在d[x]相同的时候可能算重

    所以必须以连通块来统计

    考虑在连通块的最浅的点统计

    但是第k大不一定是这个点

     

    考虑一个套路:用许多次分别统计,最后实际上凑出答案的想法。

    而且本题权值范围和n同阶。

     

    外层枚举每一个i∈[1,w],DP找到所有“包含大于等于w的点有至少k个”的连通块的个数

    所有的i的总和就是答案。每个连通块恰好统计了第k大的数权值的次数

    这样就可以在连通块最浅点统计辣。

    二、DP

    DP就很显然了

    外层枚举i∈[1,w],dp[x][j]以x为根的子树,选择了大于等于i的有j个的方案数

    卷积型背包转移。注意每个儿子还可以不选。

    最后答案是每次i的每个x的dp[x][k~n]的和的和

     

    看上去似乎优化到头了。(这时候暴力就可以AC了)

    但是,

    下面开始神仙思路了:

    “DP数组是死的,但是套上高级算法就活了”

    既然是一个卷积,不妨考虑用生成函数的思想,再用一些多项式的科技和高级数据结构等等来搞搞事情。

    现在dp是一个生成函数,首先有:

    (注意,可能x有的时候是树的节点编号,有的时候可能是生成函数里的x变量)

    $dp[x]=Pi (dp[son]+1)*Z$

    $Z=x^{[d[x]>=i]}$

    是一个多项式,当d[x]>=i的时候,Z是x,否则是0,象征x这个点自己是否能够作为>=i的点。

    看上去更差劲了,,先接着往下看

    三、整体DP

    一个强大的思想,是把许多次DP(比如这里要做w次)一起做,叫做整体DP

    一般用线段树合并维护,当前点x的线段树的叶子节点下标y的位置的权值,是第y次dp到x这里的权值

    考虑把一次影响贡献到所有的DP中。或者说,我们这次外层枚举x,内层枚举每一个DP

    线段树合并的时候,更新一些DP值。

    但是,,,

    这个题每个x的DP是一个数组,不是一个值,,,对应位置卷积,,线段树合并无能为力。。。

    生成函数的卷积太麻烦,我们把它变成点值

    最外层(主函数)枚举i=1~N+1,象征要求所有的DP的生成函数在x=i时候的点值的和。(注意是和,因为其实是对于每个x统计连通块的个数)

    现在,外层枚举到i,dfs到点cur的线段树中下标为y的叶子的f值的意义是:在第y次DP时候,dp[cur]这个生成函数在x=i时候的点值

    现在每个位置不是多项式,而是一个值辣。

    而我们还要知道所有位置的点值之和,便于统计最后答案

    线段树每个叶子再来一个g:外层枚举到i,dfs到点cur的线段树中下标为y的叶子的g值的意义是:在第y次DP时候,整个子树dp[]这个生成函数在x=i时候的点值之和

    四、线段树合并

    外层枚举i=1~N+1

    先考虑要做什么:

    1.x的每个位置先赋值为1

    2.dfs下去,合并

    3.乘上自己的贡献。前[1,d[x]]乘上i,后[d[x]+1,n]乘上1

    4.把自己的f贡献到自己的g

    4.有一个:$dp[x]=Pi (dp[son]+1)*Z$

    $dp[son]+1$所以干脆把全局f回溯前全局+1

    我们需要用线段树维护对于每个子树的对于每个叶子的变换(维护一会说

    (最后在rt统计信息不用pushup,只有pushdown)

    线段树合并时候对应位置点值相乘(因为真实的dp[x]生成函数最多n项)

    对应位置相乘没法做,但是位置同时乘上一个数可以做。由于区间操作,所以在全区间都是同一个“变换”的时候,直接给x树位置乘上变换即可。

    变换是什么?

    需要支持:

    1.维护f,维护g

    2.给f+1,给f乘一个数

    3.f加到g上去。

    设置一个变换(应该是一个群):(a,b,c,d)

    定义乘法:$(a1,b1,c1,d1) × (a2,b2,c2,d2)=(a1×a2,a2×b1+b2,c1+c2*a1,c2*b1+d2+d1)$

    定义单位元:

    $(1,0,0,0)$

    推一推发现,这个变换有结合律。可以支持标记合并。

    我们把f放在b位置,g放在d位置,就可以维护了。(暴力讨论维护一定也可以,但是绝对不如这个变换的设计好写)

    详见代码。

    五、插值部分

    我们得到的是点值。ans[i]表示最终的生成函数(把所有点的W次求的生成函数加起来)在x=i时候的点值。

    用拉格朗日插值还原,见另一篇博客。[学习笔记]拉格朗日插值

     

    六、实现细节:

    1.64123相乘爆int,用unsigned int

    2.每次可以垃圾回收,所以线段树合并用&,使得y的儿子直接变掉(看代码

     

    代码:

    #include<bits/stdc++.h>
    #define il inline
    #define reg register int
    #define ui unsigned int 
    #define mid ((l+r)>>1)
    #define numb (ch^'0')
    using namespace std;
    typedef long long ll;
    il void rd(int &x){
        char ch;bool fl=false;
        while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
        for(x=numb;isdigit(ch=getchar());x=x*10+numb);
        (fl==true)&&(x=-x);
    }
    namespace Miracle{
    const int N=1700;
    const int mod=64123;
    int n,k,w;
    int d[N];
    struct node{
        int nxt,to;
    }e[2*N];
    int hd[N],cnt;
    void add(int x,int y){
        e[++cnt].nxt=hd[x];
        e[cnt].to=y;
        hd[x]=cnt;
    }
    struct data{
        int a,b,c,d;
        data(){}
        data(int aa,int bb,int cc,int dd){
            a=aa;b=bb;c=cc;d=dd;
        }
        data friend operator *(data A,data B){
            return data((ui)A.a*B.a%mod,((ui)A.b*B.a+B.b)%mod,((ui)B.c*A.a+A.c)%mod,((ui)B.c*A.b+A.d+B.d)%mod);
        }
        void clear(){
            a=1,b=0,c=0,d=0;
        }
    };
    struct tr{
        int ls,rs;
        data val;
        tr(){val.a=1;val.b=0;val.c=0;val.d=0;ls=rs=0;}
        void clear(){val.a=1;val.b=0;val.c=0;val.d=0;ls=rs=0;}
    }t[100*N];
    int sta[100*N],top,tot;
    int nc(){
        return top?sta[top--]:++tot;
    }
    int rt[N];
    int ans[N];//dian zhi
    int b[N];//true xishu 
    int f[N];
    int c[N];
    int inv[mod+10];
    void del(int &x){
        if(!x) return;
        if(t[x].ls) del(t[x].ls);
        if(t[x].rs) del(t[x].rs);
        sta[++top]=x;
        t[x].clear();
        x=0;
    }
    void pushdown(int x){
        if(!t[x].ls) t[x].ls=nc();
        if(!t[x].rs) t[x].rs=nc();
        t[t[x].ls].val=t[t[x].ls].val*t[x].val;
        t[t[x].rs].val=t[t[x].rs].val*t[x].val;
        t[x].val.clear();
    }
    int query(int x,int l,int r){
        if(l==r) return t[x].val.d;
        pushdown(x);
        int ret=query(t[x].ls,l,mid);
        ret=(ret+query(t[x].rs,mid+1,r))%mod;
        return ret;
    }
    void upda(int &x,int l,int r,int L,int R,data c){
        if(!x) x=nc();
        if(L<=l&&r<=R){
            t[x].val=t[x].val*c;
            return;
        }
        pushdown(x);
        if(L<=mid) upda(t[x].ls,l,mid,L,R,c);
        if(mid<R) upda(t[x].rs,mid+1,r,L,R,c);
    }
    int merge(int &x,int &y){
        //if(!x||!y) return x+y;每次都会pushdown产生儿子。所以这里不需要 
        if(!t[x].ls&&!t[x].rs) swap(x,y);
        if(!t[y].ls&&!t[y].rs){
            t[x].val=t[x].val*data(t[y].val.b,0,0,0);
            t[x].val=t[x].val*data(1,0,0,t[y].val.d);
            return x;
        }
        pushdown(x);pushdown(y);
        t[x].ls=merge(t[x].ls,t[y].ls);
        t[x].rs=merge(t[x].rs,t[y].rs);
        return x;
    }
    void dfs(int x,int fa,int k0){
        upda(rt[x],1,w,1,w,data(0,1,0,0));
        for(reg i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            if(y==fa) continue;
            dfs(y,x,k0);
            rt[x]=merge(rt[x],rt[y]);
            del(rt[y]);
        }
        upda(rt[x],1,w,1,d[x],data(k0,0,0,0));
        upda(rt[x],1,w,1,w,data(1,0,1,0));
        upda(rt[x],1,w,1,w,data(1,1,0,0));
    }
    void Lagelangri(){
        f[0]=1;
        for(reg i=1;i<=n+1;++i){
            for(reg j=n+1;j>=0;--j){
                f[j]=((ui)f[j]*(mod-i)%mod+(j?f[j-1]:0))%mod;
            }
        }
        
        for(reg i=1;i<=n+1;++i){
            ll tmp=1;
            for(reg j=1;j<=n+1;++j){
                c[j]=f[j];
                if(j!=i) {
                    if(j<i) tmp=tmp*inv[i-j]%mod;
                    else tmp=tmp*(mod-inv[j-i])%mod;
                }
            }
            c[0]=f[0];
            tmp=tmp*ans[i]%mod;
            for(reg j=0;j<=n+1;++j){
                if(!j) c[j]=(mod-(ll)c[j]*inv[i]%mod)%mod;
                else c[j]=(mod-((ll)c[j]-c[j-1]+mod)*inv[i]%mod)%mod;
            } 
            
            for(reg j=0;j<=n;++j){
                b[j]=((ll)b[j]+c[j]*tmp%mod)%mod;
            }
        }
    }
    int main(){
        rd(n);rd(k);rd(w);
        int x,y;
        for(reg i=1;i<=n;++i){
            rd(d[i]);
        }
        for(reg i=1;i<n;++i){
            rd(x);rd(y);add(x,y);add(y,x);
        }
        for(reg i=1;i<=n+1;++i){
            dfs(1,0,i);
            ans[i]=query(rt[1],1,w);
            del(rt[1]);
        }
        inv[1]=1;
        for(reg i=2;i<=n+1;++i) inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;
        Lagelangri();
        ll op=0;
        for(reg i=k;i<=n;++i){
            op=(op+b[i])%mod;
        }
        printf("%lld",op);
        return 0;
    }
    
    }
    signed main(){
        Miracle::main();
        return 0;
    }
    
    /*
       Author: *Miracle*
       Date: 2019/2/19 9:45:33
    */

    总结:

    1.题意转化使得连通块可以直接在最浅的地方统计,模型简单,为后面套高级算法留下可能性

    2.生成函数思想+点值,妈妈再也不用担心卷积太复杂!~

    3.整体DP,赋值之间大同小异,所以所有DP值一起处理,

    4.拉格朗日插值O(n^2)trick

     

    神仙啊~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

     

  • 相关阅读:
    mysql select 练习题
    mysql常用函数
    创建后表的修改:添加列+修改列+删除列+重命名表+删除整张表+删除整个数据库
    数据库里any 和 all 的区别
    mysql 高级查询
    9.12/ css3拓展、js基础语法、程序基本知识、数据类型、运算符表达方式、语句知识点
    9.11知识大纲【列表拓展】【格式与布局】
    9.10
    9.9
    9.8
  • 原文地址:https://www.cnblogs.com/Miracevin/p/10402405.html
Copyright © 2011-2022 走看看