• test20200415 看门人 和 WC2010 重建计划


    test20200415 看门人

    我们可以把这个花园抽象成是一棵以 (1) 为根的,总共有 (N) 个节点的树。

    对于花园中第 (i) 个节点,看门队长定下了两个参数,(L_i,R_i) .

    AKIOI 每天需要把经过 (i) 的,且不经过这个 (i) 到节点 (1) 路径上除 (i) 外所有点的,边数在 ([L_i,R_i]) 间的所有简单路径都走一遍。

    这当然是个很累的差事了,但 AKIOI 很乐观,他觉得,只要走过的最长路径比较短,他就一点都不累。

    现在把花园的信息告诉你,对于每个点,记 (ans_i) 为 AKIOI 走过的最长路径的长度,假如不存在则 (ans_i=-1)

    需要求出 (s=(sum_{i=1}^n 23333^{n-i}cdot ans_i)mod 998244353) 的值, 需要保证输出的 (s) 满足 (0le s<998244353)

    题解

    http://jklover.hs-blog.cf/2020/04/15/test20200415/#more

    长链剖分+线段树。时间复杂度 (O(nlog n))

    CO int N=1e6+10;
    CO int64 inf=1e18;
    int L[N],R[N];
    struct edge {int y,w;};
    vector<edge> to[N];
    
    int64 dis[N];
    int mxh[N],son[N],pos[N],dfn;
    
    void dfs1(int x){
    	mxh[x]=1;
    	for(CO edge&e:to[x]){
    		dis[e.y]=dis[x]+e.w;
    		dfs1(e.y);
    		mxh[x]=max(mxh[x],mxh[e.y]+1);
    		if(mxh[e.y]>mxh[son[x]]) son[x]=e.y;
    	}
    }
    void dfs2(int x){
    	pos[x]=++dfn;
    	if(son[x]) dfs2(son[x]);
    	for(CO edge&e:to[x])if(e.y!=son[x]) dfs2(e.y);
    }
    
    namespace seg{
    	int64 val[4*N];
    	
    	#define lc (x<<1)
    	#define rc (x<<1|1)
    	#define mid ((l+r)>>1)
    	void build(int x,int l,int r){
    		val[x]=-inf;
    		if(l==r) return;
    		build(lc,l,mid),build(rc,mid+1,r);
    	}
    	void insert(int x,int l,int r,int p,int64 v){
    		val[x]=max(val[x],v);
    		if(l==r) return;
    		if(p<=mid) insert(lc,l,mid,p,v);
    		else insert(rc,mid+1,r,p,v);
    	}
    	int64 query(int x,int l,int r,int ql,int qr){
    		if(ql>qr) return -inf;
    		if(ql<=l and r<=qr) return val[x];
    		if(qr<=mid) return query(lc,l,mid,ql,qr);
    		if(ql>mid) return query(rc,mid+1,r,ql,qr);
    		return max(query(lc,l,mid,ql,qr),query(rc,mid+1,r,ql,qr));
    	}
    	#undef lc
    	#undef rc
    	#undef mid
    }
    
    int64 ans[N];
    
    void solve(int x){
    	for(CO edge&e:to[x]) solve(e.y);
    	ans[x]=seg::query(1,1,dfn,pos[x]+L[x],pos[x]+min(R[x],mxh[x]-1))-dis[x];
    	seg::insert(1,1,dfn,pos[x],dis[x]);
    	for(CO edge&e:to[x])if(e.y!=son[x]){
    		for(int i=1;i<=mxh[e.y];++i)
    			ans[x]=max(ans[x],seg::query(1,1,dfn,pos[e.y]+i-1,pos[e.y]+i-1)+
    				seg::query(1,1,dfn,pos[x]+max(L[x]-i,0),pos[x]+min(R[x]-i,mxh[x]-1))-2*dis[x]);
    		for(int i=1;i<=mxh[e.y];++i)
    			seg::insert(1,1,dfn,pos[x]+i,seg::query(1,1,dfn,pos[e.y]+i-1,pos[e.y]+i-1));
    		
    	}
    }
    
    int main(){
    	int n=read<int>();
    	for(int i=1;i<=n;++i) read(L[i]),read(R[i]);
    	for(int i=2;i<=n;++i){
    		int f=read<int>(),w=read<int>();
    		to[f].push_back({i,w});
    	}
    	dfs1(1),dfs2(1);
    	seg::build(1,1,dfn);
    	solve(1);
    	int sum=0;
    //	cerr<<"ans="<<endl;
    	for(int i=1;i<=n;++i){
    		if(ans[i]<0) ans[i]=mod-1;
    		else ans[i]%=mod;
    //		cerr<<i<<" "<<ans[i]<<endl;
    		sum=add(sum,mul(fpow(23333,n-i),ans[i]));
    	}
    	printf("%d
    ",sum);
    	return 0;
    }
    

    WC2010 重建计划

    X 国遭受了地震的重创, 导致全国的交通近乎瘫痪,重建家园的计划迫在眉睫。X 国由 (N) 个城市组成, 重建小组提出,仅需建立 (N-1) 条道路即可使得任意两个城市互相可达。于是,重建小组很快提出了一个包含 (N-1) 条道路的方案,并满足城市之间两两可达,他们还计算评估了每条道路 (e) 建设之后可以带来的价值 (v(e))

    由于重建计划复杂而艰难,经费也有一定限制。因此,政府要求第一期重建工程修建的道路数目为 (k) 条,但需满足 (L leq k leq U),即不应少于(L) 条,但不超过 (U) 条。同时,为了最大化利用率,要求建设的这些道路恰好组成一条简单路径,即所建设的 (k) 条路径可以构成一个排列 (e_1 = (p_1, q_1), e_2 = (p_2, q_2), cdots , e_k = (p_k, q_k)), 对于 (1 leq i < k), 有((q_i = p_{i+1}))

    重建小组打算修改他们的原有方案以满足要求,即在原有的 (N-1) 条道路中寻找一条路径 (S) 作为新的方案,使得新方案中的道路平均价值

    [AvgValue = frac{sum _{e in S} v(e)}{|S|} ]

    最大。这里 (v(e)) 表示道路 (e) 的价值,(|S|) 表示新方案中道路的条数。请你帮助重建小组寻找一个最优方案。 注: 在本题中 (L)(U) 的设置将保证有解。

    对于100%的数据,(N leq 100 000, 1 leq L leq U leq N-1, v_i leq 10^6)

    题解

    https://www.cnblogs.com/Khada-Jhin/p/9604319.html

    这题算是长链剖分最经典的一道题了。

    首先要找一个最优方案,直接DP显然不好做,那么考虑二分答案然后验证,因为是浮点数,要注意精度问题。

    假设当前二分的答案是k,判断答案是否满足时原式也就转化成了(frac{sum v_i}{|S|}ge k),将分母移到不等式右边得到(sum v_i ge k∗|S|)。将右边的部分移到左边就变成了(sum v_i−k∗|S| ge 0)。因为(v_i)的个数就是(|S|),因此将(k*|S|)放到(Σ)里面,判断就变成了(sum (v_i−k) ge 0)

    每次判断只要把每条边的边权和减(k),再判断能否有一条路径边权和大于等于0就好了。

    怎么判断呢?很容易想到(O(n^2))dp,设f[i][j]表示i子树中与i距离为j的链的边权和最大值,枚举另一棵子树找到链长在[L-j,R-j]之内的边权最大值。

    (O(n^2))dp显然不行,但观察到dp是可合并的以深度为下标的转移方程,因此可以用长链剖分优化成(O(n))

    怎么优化呢?首先对整棵树长链剖分求出树剖序,再把树剖序架到线段树上,因为整棵树是由所有长链组成的,每条长链因为是优先遍历所以在树剖序上是连续的一段。也就是说树剖序上的每一段都是树剖出的一条长链。那么每个点子树中每个深度的信息就可以都存到这个点往下的长链上。

    当做树形DP时,每个点对于重儿子回溯时不做任何操作,直接继承;当轻儿子回溯时枚举轻儿子每个深度的边权和最大值,在长链上找到对应区间求最大值来更新答案。然后再把这个轻儿子的信息合并到长链上。因为长链上存的是之前所有遍历过的子树合并后的信息,所以相当于每个点子树中有用的信息都在这个点往下的长链上。最后别忘了考虑从上到下的每条直链。

    这样DP是(O(n))的,再加上线段树的(O(log))和二分的(O(log))一共是(O(n log^2n))

    至于这样DP为什么是(O(n))的?因为每个点对重儿子是直接继承的,而每个点需要被DP当且仅当它是轻儿子时,这时它一定是一个长链的链头,DP的时间复杂度是这个点往下的长链长度,那么DP的总复杂度就是每条长链的链长总和,也就是(O(n))

    co int N=1e5+1;
    co double INF=1e11,eps=1e-4;
    int n,L,R;
    int head[N],to[N*2],v[N*2],nx[N*2],tot;
    int fa[N],dep[N],mxd[N],son[N],pos[N],dfn;
    void add(int x,int y,int w){
    	to[++tot]=y,v[tot]=w,nx[tot]=head[x],head[x]=tot;
    }
    void dfs1(int x){
    	dep[x]=dep[fa[x]]+1,mxd[x]=dep[x];
    	for(int i=head[x];i;i=nx[i]){
    		if(to[i]==fa[x]) continue;
    		fa[to[i]]=x;
    		dfs1(to[i]);
    		mxd[x]=std::max(mxd[x],mxd[to[i]]);
    		if(mxd[to[i]]>mxd[son[x]]) son[x]=to[i];
    	}
    }
    void dfs2(int x){
    	pos[x]=++dfn;
    	if(!son[x]) return;
    	dfs2(son[x]);
    	for(int i=head[x];i;i=nx[i]){
    		if(to[i]==fa[x]||to[i]==son[x]) continue;
    		dfs2(to[i]);
    	}
    }
    int id[N];
    double ans,dis[N],tmp[N],val[N*2],mxv[N*4];
    void build(int x,int l,int r){
    	mxv[x]=-INF;
    	if(l==r){
    		id[l]=x;
    		return;
    	}
    	int mid=(l+r)>>1;
    	build(x<<1,l,mid),build(x<<1|1,mid+1,r);
    }
    void change(int x,int l,int r,int p,double v){
    	mxv[x]=std::max(mxv[x],v);
    	if(l==r) return;
    	int mid=(l+r)>>1;
    	if(p<=mid) change(x<<1,l,mid,p,v);
    	else change(x<<1|1,mid+1,r,p,v);
    }
    double query(int x,int l,int r,int ql,int qr){
    	if(ql>qr) return -INF;
    	if(ql<=l&&r<=qr) return mxv[x];
    	int mid=(l+r)>>1;
    	if(qr<=mid) return query(x<<1,l,mid,ql,qr);
    	if(ql>mid) return query(x<<1|1,mid+1,r,ql,qr);
    	return std::max(query(x<<1,l,mid,ql,qr),query(x<<1|1,mid+1,r,ql,qr));
    }
    void tree_dp(int x){
    	change(1,1,n,pos[x],dis[x]);
    	for(int i=head[x];i;i=nx[i]) if(son[x]==to[i]){
    		dis[son[x]]=dis[x]+val[i];
    		tree_dp(son[x]);
    		break;
    	}
    	for(int i=head[x];i;i=nx[i]){
    		if(to[i]==fa[x]||to[i]==son[x]) continue;
    		dis[to[i]]=dis[x]+val[i];
    		tree_dp(to[i]);
    		for(int j=1;j<=mxd[to[i]]-dep[x];++j){
    			tmp[j]=mxv[id[pos[to[i]]+j-1]];
    			if(j<=R) ans=std::max(ans,query(1,1,n,std::max(pos[x],pos[x]+L-j),std::min(pos[x]+mxd[x]-dep[x],pos[x]+R-j))+tmp[j]-2*dis[x]);
    		}
    		for(int j=1;j<=mxd[to[i]]-dep[x];++j)
    			change(1,1,n,pos[x]+j,tmp[j]);
    	}
    	ans=std::max(ans,query(1,1,n,pos[x]+L,std::min(pos[x]+mxd[x]-dep[x],pos[x]+R))-dis[x]);
    }
    int main(){
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
    	read(n),read(L),read(R);
    	for(int i=1;i<n;++i){
    		int x=read<int>(),y=read<int>(),w=read<int>();
    		add(x,y,w),add(y,x,w);
    	}
    	dfs1(1),dfs2(1);
    	double l=0,r=1e6;
    	while(r-l>eps){
    		double mid=(l+r)/2;
    		for(int i=1;i<=tot;++i)
    			val[i]=v[i]-mid;
    		build(1,1,n);
    		ans=-INF,dis[1]=0;
    		tree_dp(1);
    		if(ans<0) r=mid;
    		else l=mid;
    	}
    	printf("%.3lf",l);
    	return 0;
    }
    
  • 相关阅读:
    list容器的sort函数
    c++ vector迭代器删除元素
    vs中属性页常用配置介绍2
    vs中属性页常用配置介绍
    error 不是类或命名空间
    实习代码编写中,一些有用的经验
    vs2015环境下生动动态链接库及使用
    STL容器使用的时机
    c++文件和流
    c++接口(抽象类,虚函数,纯虚函数)
  • 原文地址:https://www.cnblogs.com/autoint/p/10410335.html
Copyright © 2020-2023  润新知