zoukankan      html  css  js  c++  java
  • 并不对劲的复健训练-bzoj5250:loj2473:p4365:[九省联考2018]秘密袭击

    题目大意

    有一棵(n)(nleq 1666))个点的树,有点权(d_i),点权最大值为(w)(wleq 1666))。给出(k)(kleq n)),定义一个选择连通块的方案的权值为该连通块第(k)大的点权,如果该连通块大小(<k),那么该方案的权值为0。求所有选择连通块的方案的权值之和。

    题解

    考虑暴力:
    (f(S,k))表示连通块(S)中第(k)大的点权,那么答案就是(sumlimits_{i=1}^{w}i imes(sumlimits_{Sin [1,n]}[f(S,k)=i]))
    前面乘的(i)看上去很烦,考虑把([f(S,k)=i])(i)遍,就有该式=(sumlimits_{i=1}^wsumlimits_{j=1}^isumlimits_{Sin[1,n]}[f(S,k)=j])
    (j)拿到前面,就有该式=(sumlimits_{j=1}^wsumlimits_{Sin[1,n]}[f(S,k)geq j])
    (c(S,i))表示连通块(S)中大于等于(i)的点权的个数,那么当 ([f(S,k)geq i]) 时一定有([c(S,i)geq k]),式子变成(sumlimits_{i=1}^wsumlimits_{Sin[1,n]}[c(S,i)geq k])=(sumlimits_{i=1}^wsumlimits_{j=k}^nsumlimits_{Sin[1,n]}[c(S,i)==j])
    就可以设(h(i,j,x))表示 以(i)为深度连通块最小的点 且 大于等于(j)的数的个数为(x) 的连通块个数,就有答案=(sumlimits_{i=1}^{n}sumlimits_{j=1}^wsumlimits_{x=k}^n h(i,j,x))
    (h(i,j,x))的转移为:当(d_i<j)时,(h(i,j,x)=prodlimits_{vin son(i),sum y_v=x}h(v,j,y_v));当(d_igeq x)时,(h(i,j,x)=prodlimits_{vin son(i),sum y_v=x-1}h(v,j,y_v))
    以上的暴力做法转移过程类似树上背包,所以它的时间复杂度是(Theta(n^2 imes w))的。
    考虑设多项式(F_{i,j}(x)=sumlimits_{a=0}^n h(i,j,a) imes x^n),那么转移会变成卷。
    设多项式(G_{i,j}(x)=sumlimits_{vin i的子树}F_{v,j}(x))(H_i(x)=sumlimits_{j=1}^w G_{i,j}(x)),那么(H_1)(k)次项系数到第(n)次项系数就会是答案。
    发现在进行儿子到父亲的转移时,对于(jin[1,w])(f(i,j,a))的转移大同小异,考虑对于每个点(i)同时维护(F_{i,j}(x))这些多项式。
    直接维护多项式不太可行。考虑将(x=1,2,...,n+1)代入,用(y=F_{i,j}(x))来代表这个多项式进行点值的计算,最后得到(H_1(1),...,H_1(n+1)),再用拉格朗日插值还原出(H_1(x))
    初始值:当(d_igeq j)时,有(F_{i,j}=x)(即(f(i,j,1)=1));当(d_i< j)时,有(F_{i,j}=1)(即(f(i,j,0)=1));
    在从(i)的儿子(v)转移到(i)时要做这么几件事:(F_{i,j}卷=(F_{v,j}+1),G_{i,j}+=G_{v,j})
    考虑完所有(i)的儿子到(i)的转移后,还应有:(G_{i,j}+=F_{i,j})
    发现(F_{i,j}卷=(F_{v,j}+1))不太好办,可以在计算完(F_{i,j},G_{i,j})后,令(F_{i,j}+=1)
    以上过程可以用线段树维护,设初始值相当于区间修改,其他的相当于全局加,从(i)的儿子转移到(i)相当于合并两棵线段树上的标记。
    具体地,要维护标记((a,b,c,d))表示(F=a imes F+b,G=c imes F+d+G)
    考虑标记的叠加:假设一个点先被标记了((a_0,b_0,c_0,d_0)),又被标记了((a_1,b_1,c_1,d_1)),就相当于(F=a_1 imes(a_0 imes F+b_0)+b_1)=((a_1 imes a_0) imes F+(a_1 imes b_0+b_1))(G=c_1 imes(a_0 imes F+b_0)+d_1+c_0 imes F+d_0+G)=((c_1 imes a_0+c_0) imes F+(c_1 imes b_0+d_1+d_0)+G),也就是它们的标记合并起来就是((a_1 imes a_0,a_1 imes b_0+b_1,c_1 imes a_0+c_0,c_1 imes b_0+d_1+d_0))
    标记叠加的性质:观察可得标记的叠加不满足交换律;假设一个点先被标记了((a_0,b_0,c_0,d_0)),又被标记了((a_1,b_1,c_1,d_1)),又被标记了((a_2,b_2,c_2,d_2)),可以通过计算((a_0,b_0,c_0,d_0)+(a_1,b_1,c_1,d_1)+(a_2,b_2,c_2,d_2))((a_0,b_0,c_0,d_0)+((a_1,b_1,c_1,d_1)+(a_2,b_2,c_2,d_2)))来证明其满足结合律,因此可以在线段树上打标记,合并时合并标记。
    整个流程是:枚举(x)取1到(n+1)(其实是随便(n+1)个数),从一号点(其实是随便一个点)开始DFS。每走到一个点,区间加初始值,走该点的每个儿子,走完每个儿子后线段树合并,合并后可以回收不用的点。走完所有儿子后进行一些小的处理(如(G+=F,F++))。把每个点走完后,计算(H_1(x)=sum_{j=1}^w G_{1,j}(x))。枚举完(x)后,得到(n+1)(H_1)的点值,做一遍拉格朗日插值得到(H_1)每一项的系数,计算(k)次项至(n)次项系数的和。

    代码
    #include<algorithm>
    #include<cmath>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<ctime>
    #include<iomanip>
    #include<iostream>
    #include<map>
    #include<queue>
    #include<set>
    #include<stack>
    #include<vector>
    #define rep(i,x,y) for(register int i=(x);i<=(y);++i)
    #define dwn(i,x,y) for(register int i=(x);i>=(y);--i)
    #define view(u,k) for(int k=fir[u];~k;k=nxt[k])
    #define LL long long
    #define maxn 1677
    #define ls son[u][0]
    #define rs son[u][1]
    #define Ls(u) son[u][0]
    #define Rs(u) son[u][1]
    #define UI unsigned int
    #define mi (l+r>>1)
    using namespace std;
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)&&ch!='-')ch=getchar();
        if(ch=='-')f=-1,ch=getchar();
        while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
        return x*f;
    }
    void write(UI x)
    {
        if(x==0){putchar('0'),putchar('
    ');return;}
        int f=0;char ch[20];
        if(x<0)putchar('-'),x=-x;
        while(x)ch[++f]=x%10+'0',x/=10;
        while(f)putchar(ch[f--]);
        putchar('
    ');
        return;
    }
    const UI mod=64123;
    UI ans,f[maxn],qy[maxn],b[maxn];
    int n,K,w,d[maxn],rt[maxn],fir[maxn],nxt[maxn<<1],v[maxn<<1],cnt,son[maxn<<6][2],rec[maxn<<6],tpr,cntnd;
    struct node{UI a,b,c,d;}tr[maxn<<6];
    UI mo(UI x){return x>=mod?x-mod:x;}
    void ade(int u1,int v1){v[cnt]=v1,nxt[cnt]=fir[u1],fir[u1]=cnt++;}
    UI mul(UI x,int y){UI res=1;while(y){if(y&1)res=res*x%mod;x=x*x%mod,y>>=1;}return res;}
    void pu(node & x,node y,node z)
    {
    	x.a=y.a*z.a%mod;
    	x.b=mo(z.a*y.b%mod+z.b);
    	x.c=mo(z.c*y.a%mod+y.c);
    	x.d=mo((z.c*y.b%mod+z.d)+y.d);
    }
    node getnd(UI a,UI b,UI c,UI d){node tmp;tmp.a=a,tmp.b=b,tmp.c=c,tmp.d=d;return tmp;}
    int newnd(){int u=tpr?rec[tpr--]:++cntnd;tr[u].a=1,tr[u].b=tr[u].c=tr[u].d=ls=rs=0;return u;}
    void pd(int u)
    {
    	if(!ls)ls=newnd();
    	pu(tr[ls],tr[ls],tr[u]);
    	if(!rs)rs=newnd();
    	pu(tr[rs],tr[rs],tr[u]);
    	tr[u].a=1,tr[u].b=tr[u].c=tr[u].d=0;
    }
    int add(int u,int l,int r,int x,int y,node k)
    {
    	if(!u)u=newnd();
    	if(x<=l&&r<=y){pu(tr[u],tr[u],k);return u;}
    	pd(u);
    	if(x<=mi)ls=add(ls,l,mi,x,y,k);
    	if(y>mi)rs=add(rs,mi+1,r,x,y,k);
    	return u;	
    }
    int merge(int ua,int ub,int l,int r)
    {
    	if(!ua||!ub){if(!ua)swap(ua,ub);rec[++tpr]=ub;return ua;}
    	if(!Ls(ua)&&!Rs(ua))swap(ua,ub);
    	if(!Ls(ub)&&!Rs(ub))
    	{
    		pu(tr[ua],tr[ua],getnd(tr[ub].b,0,0,0));
    		pu(tr[ua],tr[ua],getnd(1,0,0,tr[ub].d));
    		rec[++tpr]=ub;
    		return ua;
    	}
    	pd(ua),pd(ub);
    	Ls(ua)=merge(Ls(ua),Ls(ub),l,mi);
    	Rs(ua)=merge(Rs(ua),Rs(ub),mi+1,r);
    	rec[++tpr]=ub;
    	return ua;
    }
    void recy(int u){if(ls)recy(ls);rec[++tpr]=u;if(rs)recy(rs);}
    UI ask(int u,int l,int r)
    {
    	if(l==r)return tr[u].d;
    	pd(u);UI res=ask(ls,l,mi);
    	res=mo(res+ask(rs,mi+1,r));
    	return res;
    }
    void dfs(int u,int fa,UI qx)
    {
    	rt[u]=add(rt[u],1,w,1,w,getnd(0,1,0,0));
    	view(u,k)if(v[k]!=fa)
    	{
    		dfs(v[k],u,qx);
    		rt[u]=merge(rt[u],rt[v[k]],1,w);
    		rt[v[k]]=0;
    	}
    	rt[u]=add(rt[u],1,w,1,d[u],getnd(qx,0,0,0));
    	rt[u]=add(rt[u],1,w,1,w,getnd(1,0,1,0));
    	rt[u]=add(rt[u],1,w,1,w,getnd(1,1,0,0));
    }
    void getf(int sz)
    {
    	f[0]=1;
    	rep(i,1,sz)dwn(j,i,1)f[j]=mo(f[j]+(LL)f[j-1]*(mod-i)%mod);
    	reverse(f,f+sz+1);
    	rep(i,1,sz)
    	{
    		int lst=0,num=1,nyx=mul(mod-i,mod-2);
    		rep(j,1,sz)if(i!=j)num=(LL)num*mo(i-j+mod)%mod;
    		num=(LL)mul(num,mod-2)*qy[i]%mod;
    		rep(i,0,sz-1)lst=(LL)mo(f[i]-lst+mod)*nyx%mod,b[i]=mo(b[i]+(LL)lst*num%mod);
    	}
    	rep(i,K,sz-1)ans=mo(ans+b[i]);
    }
    UI ff(UI x)
    {
    	UI res=0,now=1;
    	rep(i,0,n)res=mo(res+now*b[i]%mod),now=now*x%mod;
    	return res;
    }
    int main()
    {
        memset(fir,-1,sizeof(fir));
        n=read(),K=read(),w=read();
        rep(i,1,n)d[i]=read();
        rep(i,1,n-1){int x=read(),y=read();ade(x,y),ade(y,x);}
        rep(i,1,n+1){dfs(1,0,i);qy[i]=ask(rt[1],1,w);recy(rt[1]),rt[1]=0;}
        getf(n+1);
        write(ans);
        return 0;
    }
    
    一些感想

    打开loj“统计”,按“最快”排序,可发现前面一堆暴力。

  • 相关阅读:
    Codeforces Round #201 (Div. 2)C.Alice and Bob
    1126. Magnetic Storms(单调队列)
    URAL1501. Sense of Beauty(记忆化)
    poj1026Cipher(置换群)
    怎样查看MySql数据库物理文件存放位置
    冒泡排序、选择排序、二分查找排序
    java中的数组的Arrays工具类的使用
    可变参数及其特点
    猜拳游戏项目(涉及知识点Scanner、Random、For、数组、Break、Continue等)
    java中使用nextLine(); 没有输入就自动跳过的问题?
  • 原文地址:https://www.cnblogs.com/xzyf/p/11597346.html
Copyright © 2011-2022 走看看