zoukankan      html  css  js  c++  java
  • LOJ3340 「NOI2020」命运

    题目链接

    博主有幸参加了NOI2020,考场上的经历和心得请见这篇文章。这里就不唠叨了。

    本题题解

    为了方便表述,我们把边重要、不重要,称为:颜色为(1)(0)。本题相当于要求对给边染色的方案计数。

    (40)分部分分,是对(m)个限制做容斥。也就是(2^m)枚举哪些路径强制设为(0),不在这些路径上的边可以随意。问题转化为求路径并,可以用树链剖分或虚树维护。由于与正解做法关联不大,这里不细讲了。


    正解的起点是一个DP。设(dp[u][i])表示假设(u)子树外边的颜色都已经确定,且(u)的“祖先边”((u)到根路径上所有边)中,深度最大(离它最近)的颜色为(1)的边,深度为(i),此时(u)子树内所有边合法的染色方案数。

    容易发现,一个子树内的染色方案数,只和这个子树“祖先边”中深度最大的颜色为(1)的边的深度有关,而这个东西我们已经写在了状态里(就是(i))。因此这个状态非常合理。

    更具体来讲,我们定义点的深度为它到根路径上的边数。边的深度为它连接的儿子节点的深度。因为根节点(深度为(0))没有父亲边,所以不存在深度为(0)的边。不过我们可以假装有这样一条边,且这条边的颜色永远是(1)。则DP的答案就是(dp[1][0]),其中(0)就是我们假想出来的这条边的深度。

    至于(m)条限制,我们对每个点(u),维护它“祖先边”中,离它最近的、颜色为(1)的边,深度至少是多少,记为( ext{lim}[u])。初始时,( ext{lim}[u]=0),也就是我们假想的那个颜色永远是(1)的边。每读入一条限制((u,v)),就令( ext{lim}[v]:=max( ext{lim}[v], ext{dep}[u]+1))。同时,每个节点的( ext{lim}),还要对( ext{lim}[fa(u)])(max)

    容易发现,对于(i otin [ ext{lim}[u], ext{dep}[u]])(dp[u][i]=0)

    对于其他的(i),可以从儿子转移过来。不难写出:

    [dp[u][i]=[ ext{lim}[u]leq ileq ext{dep}[u]] imes prod_{vin ext{son}(u)}(dp[v][i]+dp[v][ ext{dep}[v]]) ]

    这样直接DP是(O(n^2))的。


    继续优化,对于每个点(u),考虑把(dp[u])的第二维搬到线段树上,然后做线段树合并。考虑在转移过程中,我们的线段树需要做哪些操作。

    1. 假设线段树初值全部为(0)。那我们要先把([ ext{lim}[u], ext{dep}[u]])这个区间赋值为(1)
    2. 我们要对所有(0leq i< ext{dep}[u]),令(dp[u][i] exttt{+=}dp[u][ ext{dep[u]}]),这是为了从(u)向父亲转移时更方便(看一下转移式就知道了)。
    3. 在第2条的基础上,转移时,我相当于要合并两棵线段树,并实现把所有对应位置相乘。

    前两条操作,可以看成区间加。第3条操作又涉及乘法。所以我们的线段树,维护区间加法和乘法两个懒标记。

    这里需要注意一下,下放标记的顺序。更新乘法标记时,把已有的加法标记也乘上这个数。更新加法标记时则不动乘法标记。下放标记(push_down)时,先下放乘法标记,再下放加法标记。这里面的原因并不复杂,可以自行思考一下。

    同时我们还要支持单点查询。这个在访问到线段树叶子节点时,直接反回它的加法标记值即可。

    这样合并(O(n))次,做(O(n))次修改,时间复杂度是(O(nlog n))的。

    参考代码(建议使用读入优化,详见本博客公告):

    //problem:LOJ3340
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
    template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
    
    const int MAXN=5e5,MAXM=5e5;
    const int MOD=998244353;
    inline int mod1(int x){return x<MOD?x:x-MOD;}
    inline int mod2(int x){return x<0?x+MOD:x;}
    inline void add(int& x,int y){x=mod1(x+y);}
    inline void sub(int& x,int y){x=mod2(x-y);}
    inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
    
    int n,m;
    vector<int>G[MAXN+5];
    struct Request{
    	// u是祖先,v是后代
    	int u,v;
    }q[MAXM+5];
    
    int fa[MAXN+5],dep[MAXN+5];
    int lim[MAXN+5];
    void dfs_prep(int u){
    	for(int i=0;i<SZ(G[u]);++i){
    		int v=G[u][i];
    		if(v==fa[u]) continue;
    		fa[v]=u;
    		dep[v]=dep[u]+1;
    		dfs_prep(v);
    	}
    }
    
    struct SegmentTree{
    	int rt[MAXN+5];
    	int cnt;
    	vector<int>bin;
    	int ls[MAXN*100+5],rs[MAXN*100+5];
    	int tag_add[MAXN*100+5],tag_mul[MAXN*100+5];
    	int new_node(int _mul=1,int _add=0){
    		int x;
    		if(SZ(bin)){
    			x=bin.back();
    			bin.pop_back();
    		}
    		else{
    			x=++cnt;
    		}
    		ls[x]=rs[x]=0;
    		tag_mul[x]=_mul;
    		tag_add[x]=_add;
    		return x;
    	}
    	void upd_mul(int x,int v){
    		tag_mul[x]=(ll)tag_mul[x]*v%MOD;
    		tag_add[x]=(ll)tag_add[x]*v%MOD;
    	}
    	void upd_add(int x,int v){
    		add(tag_add[x],v);
    	}
    	void push_down(int x){
    		if(tag_mul[x]!=1){
    			if(!ls[x]) ls[x]=new_node();
    			if(!rs[x]) rs[x]=new_node();
    			upd_mul(ls[x],tag_mul[x]);
    			upd_mul(rs[x],tag_mul[x]);
    			tag_mul[x]=1;
    		}
    		if(tag_add[x]){
    			if(!ls[x]) ls[x]=new_node();
    			if(!rs[x]) rs[x]=new_node();
    			upd_add(ls[x],tag_add[x]);
    			upd_add(rs[x],tag_add[x]);
    			tag_add[x]=0;
    		}
    	}
    	void modify_add(int& x,int l,int r,int ql,int qr,int v){
    		if(ql<=l && qr>=r){
    			upd_add(x,v);
    			return;
    		}
    		int mid=(l+r)>>1;
    		push_down(x);
    		if(ql<=mid)
    			modify_add(ls[x],l,mid,ql,qr,v);
    		if(qr>mid)
    			modify_add(rs[x],mid+1,r,ql,qr,v);
    	}
    	int query(int x,int l,int r,int pos){
    		if(l==r){
    			return tag_add[x];
    		}
    		int mid=(l+r)>>1;
    		push_down(x);
    		if(pos<=mid)
    			return query(ls[x],l,mid,pos);
    		else
    			return query(rs[x],mid+1,r,pos);
    	}
    	int merge(int x, int y){
    		if (!ls[x] && !rs[x]) swap(x, y);
    		if (!ls[y] && !rs[y]){
    			upd_mul(x,tag_add[y]);
    			return x;
    		}
    		push_down(x), push_down(y);
    		ls[x] = merge(ls[x], ls[y]);
    		rs[x] = merge(rs[x], rs[y]);
    		return x;
    	}
    	SegmentTree(){}
    }T;
    /*
    int dp[MAXN+5][MAXN+5];
    void dfs_dp(int u){
    	for(int j=lim[u];j<=dep[u];++j){
    		dp[u][j]=1;
    	}
    	for(int i=0;i<SZ(G[u]);++i){
    		int v=G[u][i];
    		if(v==fa[u]) continue;
    		dfs_dp(v);
    		for(int j=lim[u];j<=dep[u];++j){
    			dp[u][j]=(ll)dp[u][j]*(dp[v][j]+dp[v][dep[v]])%MOD;
    		}
    	}
    }
    */
    void dfs_dp(int u){
    	lim[u]=max(lim[u],lim[fa[u]]);
    	T.rt[u]=T.new_node(0,0);
    	T.modify_add(T.rt[u],0,n-1,lim[u],dep[u],1);
    	for(int i=0;i<SZ(G[u]);++i){
    		int v=G[u][i];
    		if(v==fa[u]) continue;
    		dfs_dp(v);
    		T.rt[u]=T.merge(T.rt[u],T.rt[v]);
    	}
    	if(u==1) return;
    	int v=T.query(T.rt[u],0,n-1,dep[u]);
    	T.modify_add(T.rt[u],0,n-1,0,dep[u]-1,v);
    }
    int main() {
    //	freopen("destiny.in","r",stdin);
    //	freopen("destiny.out","w",stdout);
    	cin>>n;
    	for(int i=1;i<n;++i){
    		int u,v;
    		cin>>u>>v;
    		G[u].pb(v);
    		G[v].pb(u);
    	}
    	dfs_prep(1);
    	cin>>m;
    	for(int i=1;i<=m;++i){
    		cin>>q[i].u>>q[i].v;
    		ckmax(lim[q[i].v],dep[q[i].u]+1);
    	}
    	dfs_dp(1);
    	cout<<T.query(T.rt[1],0,n-1,0)<<endl;
    	return 0;
    }
    
  • 相关阅读:
    servlet的之前与之后的基本使用
    java HashMap插入重复Key值问题
    ConcurrentHashMap底层实现原理(JDK1.7 & 1.8)
    spring cloud实现热加载
    spring cloud各个组件以及概念的解释和基本使用
    深入理解java 虚拟机 jvm高级特性与最佳实践目录
    【leetcode】1、两数之和
    【Java 基础领域】二维数组创建内存图
    【Java EE领域】com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'salary' in 'fi
    【JavaEE领域】com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'mp.employee' doesn't exi
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13557336.html
Copyright © 2011-2022 走看看