树上数据结构选讲

CodeChef - BTREE Union on T

首先可以很自然地想到把虚树建出来然后在上面搞。

我们做两遍 \(\text{dp}\),把每个点的 \(r_i\) 更新成从这个点出来能覆盖的最远距离和从其他点出来经过这个点后能够覆盖的最远距离的最大值。

这样我们保证了对于一条边 \((u,v)\)\(u\)\(v\) 的父亲),一定存在一个点 \(w\) 使得 \(v\)\(u\) 更新 \(w\) 更优。

那么我们先计算出所有更新后 \(U(x_i,r_i)\) 能够覆盖到的点的数目。

这样子肯定会算重,我们再考虑把算重的减掉。

算重的部分相当于找到上文说的那个 \(w\),计算有多少点在 \(w\) 上面并且被 \(v\) 的范围包含,以及在 \(w\) 下面并且被 \(u\) 的范围包含。

\(r_u−(dep_w−dep_u)=r_v−(dep_v−dep_w)\) 可以确定 \(w\) 的位置。

同时可以发现 \(w\) 往上往下延伸的范围是相等的,这就相当于是 \(U(w,r_u−(dep_w−dep_u))\)

那么我们现在需要做的就是求 \(|U(x,r)|\)

这个可以建出点分树然后暴力跳父亲一层层统计。

注意到 \(w\) 可能在边上,所以一开始化边为点即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
int ver[200005],ne[200005],head[100005],tot;
inline void link(int x,int y){
	ver[++tot]=y;
	ne[tot]=head[x];
	head[x]=tot;
}
namespace LCA{
	int dep[100005],fa[100005],dfn[100005],cnt,siz[100005],son[100005]; 
	void dfs1(int x,int fi){
		if(x<=n)dfn[x]=++cnt;
		fa[x]=fi;dep[x]=dep[fi]+1;siz[x]=1;
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(u==fi)continue;
			dfs1(u,x);siz[x]+=siz[u];
			if(siz[u]>siz[son[x]])son[x]=u;
		}
		if(x<=n)dfn[x+n]=++cnt;
	}
	int top[100005];
	vector<int> vec[100005];
	void dfs2(int x,int fi){
		top[x]=fi;vec[fi].push_back(x);
		if(son[x])dfs2(son[x],fi);
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(u==fa[x]||u==son[x])continue;
			dfs2(u,u);
		}
	}
	inline int lca(int x,int y){
		while(top[x]!=top[y]){
			if(dep[top[x]]>=dep[top[y]])x=fa[top[x]];
			else y=fa[top[y]];
		}
		return dep[x]<dep[y]?x:y;
	}
	inline int Getk(int x,int k){
		while(k>dep[x]-dep[top[x]]){
			k-=dep[x]-dep[top[x]]+1;
			x=fa[top[x]];
		}
		return vec[top[x]][dep[x]-dep[top[x]]-k];
	}
}
namespace Base{
	int siz[100005],mxp[100005],rt;
	bool vis[100005];
	void findrt(int x,int fi,int tot){
		siz[x]=1;mxp[x]=0;
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(vis[u]||u==fi)continue;
			findrt(u,x,tot);siz[x]+=siz[u];
			mxp[x]=max(mxp[x],siz[u]);
		}
		mxp[x]=max(mxp[x],tot-siz[x]);
		if(mxp[x]<mxp[rt])rt=x;
	}
	vector<int> tmp1[100005],tmp2[100005];
	vector<pair<int,int> > fa[100005];
	void dfs(int x,int fi,int dep,int top1,int top2){
		if(x<=n){
			tmp1[top1].push_back(dep);
			tmp2[top2].push_back(dep);
		}
		fa[x].push_back(make_pair(top1,dep));
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(vis[u]||u==fi)continue;
			dfs(u,x,dep+1,top1,top2);
		}
	}
	inline void clear(vector<int> &vec){
		int lim=0;
		for(auto it:vec)lim=max(lim,it);
		vector<int>tmp(lim+1);
		for(auto it:vec) tmp[it]++;
		for(int i=1;i<=lim;i++)tmp[i]+=tmp[i-1];
		vec=tmp;
	}
	void solve(int x){
		vis[x]=1;if(x<=n)tmp1[x].push_back(0);
		fa[x].push_back(make_pair(x,0));
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(vis[u])continue;
			mxp[rt=0]=n;findrt(u,u,siz[u]);
			dfs(u,x,1,x,rt);
			solve(rt);
		}
		clear(tmp1[x]);clear(tmp2[x]);
//		cout<<x<<":"<<endl;
//		for(auto it:tmp1[x])cout<<it<<" ";cout<<endl;
//		for(auto it:tmp2[x])cout<<it<<" ";cout<<endl;
	}
	inline int Get(const vector<int> vec,int x){
		if(x<0)return 0;x=min(x,(int)vec.size()-1);
		return vec[x];
	}
	inline int query(int x,int v){
		int res=0,pre=-1;
//		cout<<"query "<<x<<" "<<fa[x].size()<<endl;
		for(auto it:fa[x]){
			res+=Get(tmp1[it.first],v-it.second);
			res-=Get(tmp2[it.first],pre);pre=v-it.second;
		}
		return res;
	}
	inline void build(){
		for(int i=1;i<n;i++){
			int x,y;scanf("%d%d",&x,&y);
			link(x,i+n);link(i+n,x);
			link(y,i+n);link(i+n,y);
		}
		LCA::dfs1(1,1);
		LCA::dfs2(1,1);
		mxp[rt=0]=n;findrt(1,1,n);solve(rt);
	}
}
int r[1000005];
namespace VTree{
	inline bool cmp(int x,int y){
		return LCA::dfn[x]<LCA::dfn[y];
	}
	bool vis[100005];
	inline int calc(vector<int> tmp){
		for(auto it:tmp)vis[it]=1;
		sort(tmp.begin(),tmp.end(),cmp);
		vector<int> Tmp=tmp;
		for(int i=1;i<tmp.size();i++){
			int lc=LCA::lca(tmp[i-1],tmp[i]);
			if(!vis[lc])vis[lc]=1,Tmp.push_back(lc);
		}
		tmp=Tmp;for(auto it:Tmp)tmp.push_back(it+n);
		sort(tmp.begin(),tmp.end(),cmp);
		vector<int> stk;
		for(auto it:tmp){
			if(it<=n)stk.push_back(it);
			else {
				int u=stk.back();stk.pop_back();
				if(stk.empty())break;
				int x=stk.back();
				r[x]=max(r[x],r[u]-LCA::dep[u]+LCA::dep[x]);
			}
		}
		for(auto it:tmp){
			if(it<=n){
				int x=stk.back();
				r[it]=max(r[it],r[x]-LCA::dep[it]+LCA::dep[x]);
				stk.push_back(it);
			}
			else stk.pop_back();
		}
		long long res=0;
		for(auto it:Tmp)res+=Base::query(it,r[it]);
		for(auto it:tmp){
			if(it<=n)stk.push_back(it);
			else {
				int u=stk.back();stk.pop_back();
				if(stk.empty())break;
				int x=stk.back(),mid=(r[u]-r[x]+LCA::dep[u]-LCA::dep[x])/2;
				res-=Base::query(LCA::Getk(u,mid),r[u]-mid);
			}
		}
		for(auto it:tmp)r[it]=-1;
		for(auto it:tmp)vis[it]=0;
		return res;
	}
}
inline int read(){
	int res=0;char c=getchar();
	while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9')res=10*res+c-'0',c=getchar();
	return res;
}
int main(){
//	freopen("1.in","r",stdin);
	n=read();
	Base::build();
	memset(r,-1,sizeof(r));
	q=read();
	while(q--){
		int k;k=read();
		vector<int> vec;
		for(int j=0;j<k;j++){
			int x;x=read();r[x]=read();
			vec.push_back(x);r[x]<<=1;
		}
		printf("%d\n",VTree::calc(vec));
	}

	return 0;
}


CF1458F Range Diameter Sum

这里需要引入一套树上圆理论,我们考虑把树上的一些点看成坐标系上的一些点。直径就是覆盖他们最小圆的直径。

那么圆心就是树上所有直径的中点,半径就是直径的一半。

两个圆合并可以采用平面坐标系上圆合并的理论。

以下为了表述方便使用 \((v, r)\) 来表示一个圆。

\( C_1 + C_2 = \)

  • \(\operatorname{dist}(v_1, v_2) +\ r_1 \le r_2 \rightarrow C_2\)

  • \(\operatorname{dist}(v_1, v_2) +\ r_2 \le r_1 \rightarrow C_1\)

  • \(\text{otherwise} \rightarrow (\text{go}(v_1, v_2, \dfrac{1}{2}(\operatorname{dist}(v_1, v_2) - r_1 + r_2)), \dfrac{1}{2}(\operatorname{dist}(v_1, v_2) + r_1 + r_2))\)

这里 \(\text{go}(v_1, v_2, k)\) 是指在 \((v_1, v_2)\) 的路径上从 \(v_1\) 出发走 \(k\) 步。

回到题目本身。

我们考虑这类问题的普遍解法:枚举右端点,维护所有左端点的和。

但很遗憾的是你移动一次右端点就要修改所有左端点的值。这个是没法优化复杂度的。

于是我们应该想到整体处理——分治。

我们考虑计算区间 [l, r][l,r] 中跨过 midmid 的线段的值。

定义 \(C_i = (i, 0),\ L_i = \sum_{j=i}^{mid}C_j,\ R_i = \sum_{j=mid}^{i}C_j\) 。也就是说我们要把所有 \(L\)\(R\) 拼起来,即计算 \(\sum_{i=l}^{mid}\sum_{j=mid+1}^{r}(L_i+R_i)\)

容易一遍循环把所有 \(L, R\) 都处理出来。我们考虑枚举从右往左每个 \(L\) 计算值。

然后我们惊奇地发现,\(R\) 中的圆一定会被分成三段。左边一段全部为合并时的情况 \(2\),中间一段全部为合并时的情况 \(3\),右边一段全部为合并时的情况 \(1\)。而且这两个分界线都是右移的。

于是我们的问题就变成了维护一个点集 \(S\),查询对于点 \(u\)\(\sum_{v\in S} \operatorname{dist}(u, v)\) ,可以用点分树解决。

点击查看代码

GYM102391K. Wind of Change

对一棵树建可持久化边分树,在第二棵树上可持久化边分树合并即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
namespace T1{
	int rt[1000005],le[50000005],ri[50000005],all;
	long long tree[50000005],dep[1000005],ans[1000005];
	inline void insert(int x,int y){
		int now=rt[x];
		while(1){
			if(le[now]){
				if(!le[y])le[y]=++all,tree[all]=1e18;
				tree[le[y]]=min(tree[le[y]],tree[le[now]]+dep[x]);
				now=le[now];y=le[y];
			}else if(ri[now]){
				if(!ri[y])ri[y]=++all,tree[all]=1e18;
				tree[ri[y]]=min(tree[ri[y]],tree[ri[now]]+dep[x]);
				now=ri[now];y=ri[y];
			}else break;
		}
	}
	inline void del(){
		tree[all]=1e18;le[all]=ri[all]=0;all--;
	}
	inline long long query(int x,int y){
		int now=rt[x];long long res=1e18;
		while(1){
			if(le[now]){
				res=min(res,tree[le[now]]+dep[x]+tree[ri[y]]);
				now=le[now];y=le[y];
			}else if(ri[now]){
				res=min(res,tree[ri[now]]+dep[x]+tree[le[y]]);
				now=ri[now];y=ri[y];
			}else break;
		}
		return res;
	}
	inline void calc(vector<int> &le,vector<int> &ri){
		int tmp=all,rtl=++all,rtr=++all;
		for(auto it:le)insert(it,rtl);
		for(auto it:ri)insert(it,rtr);
		for(auto it:le)ans[it]=min(ans[it],query(it,rtr));
		for(auto it:ri)ans[it]=min(ans[it],query(it,rtl));
		while(all>tmp)del();
	}
	int m;
	int ver[2000005],ne[2000005],head[1000005],cnt,val[2000005];
	inline void link(int x,int y,int v){
		ver[++cnt]=y;
		ne[cnt]=head[x];
		head[x]=cnt;val[cnt]=v;
	}
	vector<int> son[1000005];
	int fa[1000005];
	void init(int x,int fi){
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(u==fi)continue;
			fa[u]=val[i];
			init(u,x);son[x].push_back(u);
		}
	}
	inline void build(){
		init(1,1);cnt=1;m=n;
		for(int i=1;i<=n;i++)head[i]=0;
		for(int i=1;i<=n;i++)ans[i]=1e18;
		for(int i=1;i<=m;i++){
			if(son[i].size()<3){
				for(auto it:son[i])link(i,it,fa[it]),link(it,i,fa[it]);
			}
			else {
				int le=++m,ri=++m;
				link(i,le,0);link(le,i,0);
				link(i,ri,0);link(ri,i,0);
				for(auto it:son[i])son[le].push_back(it),swap(le,ri);
			}
		}
	}
	int siz[1000005],mxp[1000005],root;
	bool vis[2000005];
	void findrt(int x,int fi,int tot){
		siz[x]=1;
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(vis[i]||u==fi)continue;
			findrt(u,x,tot);siz[x]+=siz[u];
			mxp[i>>1]=max(tot-siz[u],siz[u]);
			if(mxp[i>>1]<mxp[root])root=(i>>1);
		}
	}
	vector<int> vec;
	void dfs(int x,int fi){
		siz[x]=1;
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(vis[i]||u==fi)continue;
			dep[u]=dep[x]+val[i];dfs(u,x);siz[x]+=siz[u];
		}if(x<=n)vec.push_back(x);
	}
	void solve(int x,int tot){
		mxp[root=0]=m;findrt(x,x,tot);
		if(!root)return ;vis[root<<1]=vis[root<<1|1]=1;
		int L=ver[root<<1],R=ver[root<<1|1];
		vec.clear();dep[L]=0;dfs(L,R);
		vector<int> le;swap(le,vec);
		vec.clear();dep[R]=val[root<<1];dfs(R,L);calc(le,vec);
		solve(L,siz[L]);solve(R,siz[R]);
	}
	inline void main(){
		tree[0]=1e18;
		for(int i=1;i<n;i++){
			int x,y,v;
			scanf("%d%d%d",&x,&y,&v);
			link(x,y,v);link(y,x,v);
		}build();
		solve(1,m);
		for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);
	}
}
namespace T2{
	int m;
	int ver[2000005],ne[2000005],head[1000005],cnt,val[2000005];
	inline void link(int x,int y,int v){
		ver[++cnt]=y;
		ne[cnt]=head[x];
		head[x]=cnt;val[cnt]=v;
	}
	vector<int> son[2000005];
	int fa[2000005],lst[1000005];
	void init(int x,int fi){
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(u==fi)continue;
			fa[u]=val[i];
			init(u,x);son[x].push_back(u);
		}
	}
	inline void build(){
		init(1,1);cnt=1;m=n;
		for(int i=1;i<=n;i++)head[i]=0;
		for(int i=1;i<=n;i++)T1::rt[i]=lst[i]=++T1::all;
		for(int i=1;i<=m;i++){
			if(son[i].size()<3){
				for(auto it:son[i])link(i,it,fa[it]),link(it,i,fa[it]);
			}
			else {
				int le=++m,ri=++m;
				link(i,le,0);link(le,i,0);
				link(i,ri,0);link(ri,i,0);
				for(auto it:son[i])son[le].push_back(it),swap(le,ri);
			}
		}
	}
	int siz[1000005],mxp[1000005],rt;
	bool vis[2000005];
	void findrt(int x,int fi,int tot){
		siz[x]=1;
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(vis[i]||u==fi)continue;
			findrt(u,x,tot);siz[x]+=siz[u];
			mxp[i>>1]=max(tot-siz[u],siz[u]);
			if(mxp[i>>1]<mxp[rt])rt=(i>>1);
		}
	}
	long long dep[1000005];
	vector<int> vec;
	void dfs(int x,int fi){
		siz[x]=1;
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(vis[i]||u==fi)continue;
			dep[u]=dep[x]+val[i];dfs(u,x);siz[x]+=siz[u];
		}if(x<=n)vec.push_back(x);
	}
	void solve(int x,int tot){
		mxp[rt=0]=m;findrt(x,x,tot);
		if(!rt)return ;vis[rt<<1]=vis[rt<<1|1]=1;
		int L=ver[rt<<1],R=ver[rt<<1|1];
		vec.clear();dep[L]=0;dfs(L,R);
		for(auto it:vec){
			T1::le[lst[it]]=++T1::all;lst[it]=T1::all;
			T1::tree[lst[it]]=dep[it];
		}
		vec.clear();dep[R]=val[rt<<1];dfs(R,L);
		for(auto it:vec){
			T1::ri[lst[it]]=++T1::all;lst[it]=T1::all;
			T1::tree[lst[it]]=dep[it];
		}solve(L,siz[L]);solve(R,siz[R]);
	}
	inline void main(){
		for(int i=1;i<n;i++){
			int x,y,v;
			scanf("%d%d%d",&x,&y,&v);
			link(x,y,v);link(y,x,v);
		}
		build();
		solve(1,m);
	}
}
int main(){
	scanf("%d",&n);
	T2::main();
	T1::main();


	return 0;
}

 

BZOJ4458. GTY的OJ

树上超级钢琴。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,rt;
int ver[1000005],ne[1000005],head[500005],cnt;
inline void link(int x,int y){
	ver[++cnt]=y;
	ne[cnt]=head[x];
	head[x]=cnt;
}
long long sum[5000005];
int fa[19][500005],dep[500005];
pair<long long,int> st[19][500005];
void dfs(int x,int fi){
	sum[x]+=sum[fi];dep[x]=dep[fi]+1;
	fa[0][x]=fi;st[0][x]=make_pair(sum[x],x);
	for(int i=1;i<19;i++){
		fa[i][x]=fa[i-1][fa[i-1][x]];
		st[i][x]=min(st[i-1][x],st[i-1][fa[i-1][x]]);
	}
	for(int i=head[x];i;i=ne[i]){
		int u=ver[i];
		dfs(u,x);
	}
}
inline pair<long long,int> query(int x,int u){
	pair<long long,int> res=make_pair(1e18,0);
	for(int i=18;~i;i--){
		if(dep[fa[i][x]]>=dep[u]){
			res=min(res,st[i][x]);
			x=fa[i][x];
		}
	}
	return min(res,st[0][x]);
}
inline int Getk(int x,int k){
	for(int i=0;i<19;i++){
		if((k>>i)&1)x=fa[i][x];
	}
	return x;
}
inline int up(int x,int u){
	for(int i=18;~i;i--){
		if(dep[fa[i][x]]>dep[u])x=fa[i][x];
	}
	return x;
}
struct node{
	int x,l,r,loc;
	long long v;
	node(int _x,int _l,int _r){
		x=_x;l=_l;r=_r;auto it=query(l,r);
		loc=it.second;v=sum[x]-it.first;
	}
	inline bool operator <(const node &b)const{
		return v<b.v;
	}
};
priority_queue<node> q;
int m,L,R;
int main(){
//	freopen("1.in","r",stdin);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		int x;scanf("%d",&x);
		if(!x)rt=i;
		else link(x,i);
	}
	for(int i=1;i<=n;i++)scanf("%lld",&sum[i]);
	dfs(rt,0);
	scanf("%d%d%d",&m,&L,&R);
	for(int i=1;i<=n;i++){
		if(dep[i]<L)continue;
		int l=Getk(i,L),r=Getk(i,R);
		q.push(node(i,l,r));
	}
	long long res=0;
	while(m--){
		auto it=q.top();q.pop();
//		cout<<it.x<<" "<<dep[it.l]<<" "<<dep[it.r]<<" "<<dep[it.loc]<<" "<<it.v<<endl;
		res+=it.v;
		if(it.l!=it.loc)q.push(node(it.x,it.l,up(it.l,it.loc)));
		if(it.r!=it.loc)q.push(node(it.x,fa[0][it.loc],it.r));
	}
	printf("%lld\n",res);

	return 0;
}




「PA 2019」Podatki drogowe

可以发现,由于图中只有 \(n-1\) 条边,若边权为 \(n^z\) ,相加时是不会发生进位的。

因此我们比较两条路径时,只需依次比较每个数字出现的个数即可。

可以用主席树维护哈希值,把单次比较的复杂度优化到 \(O(\log n)\)

考虑二分答案。

我们先进行一次边分治,预处理出所有的“半条路径”,然后排序。

这样一来,对于左边的一条路径,右边和它相加后权值在当前区间内的路径会在一个区间内。

因此,我们可以对于每个左边的路径维护可以和它匹配的右路径的区间,不必维护所有的路径对,并且可以双指针在 \(O(n\log^2n)\) (含 \(O(\log n)\) 的比较和 \(O(\log\ n)\) 的边分治)的时间复杂度内算出一条路径在当前路径集合中的排名。

考虑到边权很大,而路径只有 \(n^2\) 条,我们可以去二分路径。

由于我们不好直接找出当前路径集合的中位数,我们可以随机选出一条路径来当作中位数。

因为不同路径的长度有可能相同,我们不能等集合大小为 1 了再停止二分,我们可以人为设定一个二分次数,50~60 最好。

总时间复杂度 \(O(n\log^3n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,lim;long long k;
vector<int> rt1[400005],rt2[400005];
namespace Main{
	int le[10000005],ri[10000005],siz[10000005],cnt;
	unsigned long long tree[10000005],val[100005];
	int insert(int loc,int y,int l=1,int r=n){
		if(loc<l||loc>r)return y;
		int i=++cnt;tree[i]=tree[y]+val[loc];siz[i]=siz[y]+1;
		if(l!=r){
	    	int mid=(l+r)>>1;
    		le[i]=insert(loc,le[y],l,mid);ri[i]=insert(loc,ri[y],mid+1,r);
 		}
		return i;
	}
	inline bool cmp(int a,int b,int c,int d){
		int l=1,r=n;
		while(l!=r){
			int mid=(l+r)>>1;
			if(tree[ri[a]]+tree[ri[b]]!=tree[ri[c]]+tree[ri[d]]){
				l=mid+1;
				a=ri[a];b=ri[b];c=ri[c];d=ri[d];
			}else {
				r=mid;
				a=le[a];b=le[b];c=le[c];d=le[d];
			}
		}
		return siz[a]+siz[b]<siz[c]+siz[d];
	}
	inline bool ccmp(int x,int y){
		return cmp(x,0,y,0);
	}
	vector<int> L[400005],R[400005],pos[400005];
	struct node{
		int x,y;
		node(int _x,int _y){
			x=_x;y=_y;
		}
		inline bool operator <(const node &b)const{
			return cmp(x,y,b.x,b.y);
		}
	};
	long long ans,base=1;
	const long long md=1e9+7;
	void Get(int x,int y,int l=1,int r=n){
		if(l==r){
			base=base*n%md;ans=(ans+(siz[x]+siz[y])*base)%md;
			return ;
		}
		int mid=(l+r)>>1;
		Get(le[x],le[y],l,mid);Get(ri[x],ri[y],mid+1,r);
	}
	inline void main(){
		long long all=0;
		for(int i=1;i<=lim;i++){
			L[i].resize(rt1[i].size());R[i].resize(rt1[i].size());pos[i].resize(rt1[i].size());
			for(auto &it:R[i])it=rt2[i].size()-1;all+=1ll*rt1[i].size()*rt2[i].size();
		}
		node mid(0,0);
		for(int t=1;t<=60;t++){
			long long rk=abs(1ll*rand()*rand()+rand())%all+1;
			for(int i=1;rk&&i<=lim;i++){
				for(int j=0;j<rt1[i].size();j++){
					int tmp=R[i][j]-L[i][j]+1;
					if(tmp>=rk){
						mid=node(rt1[i][j],rt2[i][L[i][j]+rk-1]);
						rk=0;break;
					}else rk-=tmp;
				}
			}
			long long tmp=0;
			for(int i=1;i<=lim;i++){
				int r=rt2[i].size()-1;
				for(int j=0;j<rt1[i].size();j++){
					r=min(r,R[i][j]);
					while(r>=L[i][j]&&mid<node(rt1[i][j],rt2[i][r]))r--;
					pos[i][j]=r;tmp+=r-L[i][j]+1;
				}
			}
			if(k==tmp)break;
			else if(k<tmp){
				all=tmp;
				for(int i=1;i<=lim;i++){
					for(int j=0;j<rt1[i].size();j++)R[i][j]=pos[i][j];
				}
			}else {
				k-=tmp;all-=tmp;
				for(int i=1;i<=lim;i++){
					for(int j=0;j<rt1[i].size();j++)L[i][j]=pos[i][j]+1;
				}
			}
		}
		Get(mid.x,mid.y);
		printf("%lld",ans);
	}
}
namespace Init{
	int ver[400005],ne[400005],head[200005],cnt=1,val[400005];
	inline void link(int x,int y,int v){
		ver[++cnt]=y;
     	ne[cnt]=head[x];
		head[x]=cnt;val[cnt]=v;
	}
	vector<pair<int,int> > son[100005];
	int las[100005];
	void init(int x,int fi){
		for(auto it:son[x]){
			int u=it.first;if(u==fi)continue;
			init(u,x);
			if(!las[x])link(x,u,it.second),link(u,x,it.second),las[x]=x;
			else {
				link(las[x],++m,0);link(m,las[x],0);las[x]=m;
				link(las[x],u,it.second);link(u,las[x],it.second);
			}
		}
	}
	int siz[200005],mxp[200005],root;
	bool vis[400005];
	void findrt(int x,int fi,int tot){
		siz[x]=1;
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];if(vis[i]||u==fi)continue;
			findrt(u,x,tot);siz[x]+=siz[u];mxp[i>>1]=max(tot-siz[u],siz[u]);
			if(mxp[root]>mxp[i>>1])root=(i>>1);
		}
	}
	int rt[200005];
	vector<int> vec;
	void dfs(int x,int fi){
		if(x<=n)vec.push_back(rt[x]);
		siz[x]=1;
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(vis[i]||u==fi)continue;
			rt[u]=Main::insert(val[i],rt[x]);
			dfs(u,x);siz[x]+=siz[u];
		}
	}
	void solve(int x,int tot){
		mxp[root=0]=m;findrt(x,x,tot);
		if(!root)return ;vis[root<<1]=vis[root<<1|1]=1;
		int L=ver[root<<1],R=ver[root<<1|1];++lim;
		rt[L]=0;vec.clear();dfs(L,R);
		sort(vec.begin(),vec.end(),Main::ccmp);swap(rt1[lim],vec);
		rt[R]=Main::insert(val[root<<1],0);vec.clear();dfs(R,L);
		sort(vec.begin(),vec.end(),Main::ccmp);swap(rt2[lim],vec);
		solve(L,siz[L]);solve(R,siz[R]);
	}
	inline void main(){
		for(int i=1;i<=n;i++)Main::val[i]=1ll*rand()*rand()*rand()+1ll*rand()*rand()+rand();
		for(int i=1;i<n;i++){
			int x,y,v;
			scanf("%d%d%d",&x,&y,&v);
			son[x].push_back(make_pair(y,v));
			son[y].push_back(make_pair(x,v));
		}m=n;
		init(1,1);solve(1,m);
	}
}
int main(){
	scanf("%d%lld",&n,&k);
	Init::main();
	Main::main();

	return 0;
}

[IOI2020] 嘉年华奖券

可以发现:对于一次取得升序序列 \(a\),其权值为 \(∑_{i=\frac{n}{2}+1}^n\limits a_i−∑_{i=1}^{\frac{n}{2}}\limits a_i\)

那么一定是在满足每种颜色 \(k\) 个的颜色下,最大的 \(\frac{nk}{2}\) 个为前面,其余为后面。

具体的,我们首先假设所有的数都是负的,然后考虑将一个数转化为正的,那么最优就是转化为所处颜色最大值。因为这个有单调性,所以可以用堆维护。

这样就可以求出答案。

接下来考虑怎么构造。

有一个显然的结论:前面的任何一个数都不小于后面的任何一个数,因为如果有一个小于的,那么就可以两边互换,就不是最优。

有了这个结论,就可以任意分组,但是还有颜色的限制。

这个限制考虑仍然用堆维护,找到含有前面元素个数最多的颜色,最先使用,其所对应的也是后面颜色最少的,这样能保证一定能匹配。

时间复杂度 \(O(nm\log m)\)

点击查看代码
#include<bits/stdc++.h>
#include "tickets.h"
using namespace std;
void allocate_tickets(vector<std::vector<int> > s);
int a[1505][1505],h[1505];
priority_queue<pair<int,int> >q;
bool vis[1505];
long long find_maximum(int k,vector<std::vector<int> > x){
	int n=x.size(),m=x[0].size();
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++)a[i+1][j+1]=x[i][j];
	}
	for(int i=1;i<=n;i++){
		sort(a[i]+1,a[i]+m+1);
		h[i]=k;q.push(make_pair(a[i][k]+a[i][m],i));
	}
	for(int i=1;i<=n*k/2;i++){
		auto it=q.top();q.pop();
		h[it.second]--;
		if(h[it.second])q.push(make_pair(a[it.second][h[it.second]]+a[it.second][m-k+h[it.second]],it.second));
	}
	while(!q.empty())q.pop();
	long long res=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=h[i];j++)res-=a[i][j];
		for(int j=m-k+h[i]+1;j<=m;j++)res+=a[i][j];
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++)x[i][j]=-1;
	}
	for(int i=1;i<=n;i++)q.push(make_pair(k-h[i],i));
	for(int i=1;i<=k;i++){
		vector<pair<int,int> > vec;
		for(int j=1;j<=n/2;j++){
			auto it=q.top();q.pop();vis[it.second]=1;
			x[it.second-1][m-it.first]=i-1;it.first--;vec.push_back(it);
		}
		for(auto it:vec)q.push(it);
		for(int j=1;j<=n;j++){
			if(!vis[j])x[j-1][--h[j]]=i-1;
			else vis[j]=0;
		}
	}
	allocate_tickets(x);
	return res;
}

CF309E Sheep

二分+贪心,二分之后,我们考虑如何构造一组解或判断不存在。

定义一个数组 \(\text{lim}[x]\) 表示 \(x\) 这个区间最晚在排列的什么位置,这个每次暴力维护,然后对于要填的每个位置,统计 \(lim\) 小于等于某个数的区间数。

找到一个最小的 \(t\) 满足恰好能将这些区间一一对应前 \(t\) 个排列的位置,然后在其中找一个右端点最小的放在 \(i\) 处就可以了,如果放不下就是无解,时间复杂度 \(O(n^2logn)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
int l[2005],r[2005],cnt[2005],lim[2005],tmp[2005];
bool vis[2005];
inline bool check(int mid){
	r[0]=1e9;
	for(int i=1;i<=n;i++)lim[i]=n;
	for(int i=1;i<=n;i++)vis[i]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++)cnt[j]=0;
		for(int j=1;j<=n;j++)if(!vis[j])cnt[lim[j]]++;
		for(int j=1;j<=n;j++)cnt[j]+=cnt[j-1];
		for(int j=1;j<=n;j++)if(cnt[j]>max(0,j-i+1))return 0;
		int p=0,rt=0;
		for(int j=n;j>=i;j--)if(cnt[j]==j-i+1)p=j;
		for(int j=1;j<=n;j++)if(!vis[j]&&r[j]<r[rt]&&lim[j]<=p)rt=j;
		tmp[i]=rt;vis[rt]=1;
		for(int j=1;j<=n;j++)if(l[j]<=r[rt]&&l[rt]<=r[j])lim[j]=min(lim[j],i+mid);
	}
	return 1;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&l[i],&r[i]);
	int l=0,r=n,res=-1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid))r=mid-1,res=mid;
		else l=mid+1;
	}
	check(res);
	for(int i=1;i<=n;i++)printf("%d ",tmp[i]);

	return 0;
}




【集训队作业2018】三角形

首先,在结点 \(u\) 放上 \(w[u]\) 个石子后,出于贪心考虑,下一步一定会把 \(u\) 的所有儿子 \(v\) 上的石子收回手中。

转换题意:

\(cnt\) 为当下树上的石子数,对每个结点 \(u\) 可以执行一次操作:

  • \(\text{step1.}\) \(cnt+=w[u]\)
  • \(\text{step2.}\) \(cnt-=\sum_{v\in son(u)}\)

当且仅当对 \(u\) 的所有儿子 \(v\) 都执行过操作,才能对 \(u\) 执行操作。

问历史上 \(cnt\) 的最大值最小可以是多少?

先不考虑儿子都操作完父亲才能操作的限制,思考一个简单版问题:有 \(n\) 个操作 \((a[i],b[i])\),其含义为:先令 \(cnt+=a[i]\),再令 \(cnt+=b[i]\)。对这 \(n\) 个操作排序,使历史上 \(cnt\) 的最大值最小。(\(a[i]=w[i],b[i]=-\sum w[v]\))。

对于一段操作 \(i,i+1,...,j\),设经过这段操作后 \(cnt=cnt+delta\),且执行这段操作途中历史上 \(cnt\)的最大值为 \(cnt+mx\)

那么每次向后添加一个二元组 (\(a[j+1]=w[j+1],b[j+1]=-\sum w[v]\)),就相当于

\[delta\leftarrow delta+w[j+1]-\sum w[v]\\ mx\leftarrow max(mx,mx+w[j+1]) \]

所以我们换一种方式,用二元组 (\(w[i]-\sum w[v],w[i]\)) 来描述操作 \(i\)。(相当于操作段 \(i\) 的 (\(delta,mx\)))

由上面可以发现这个二元组是可以简易复合的。我们的问题就是给这些二元组安排一个优先级,使得从左到右复合之后 \(mx\) 最小。

由上面可以发现这个二元组是可以简易复合的。我们的问题就是给这些二元组安排一个优先级,使得从左到右复合之后 \(mx\) 最小。

可以证明,对于二元组 \(A,B\),定义 \(+\) 为复合操作, \(A\) 的优先级比 \(B\) 高当且仅当 \((A+B).\text{max}<(B+A).\text{max}\),且这样保证不存在二元组 \(A,B,C\) 满足 \(A<B,B<C,C<A\) 。证明需要进行分类讨论:

因为复合顺序改变 \(\text{delta}\) 不变,所以只需考虑复合后的 \(\text{mx}\),显然 \(\text{mx}\) 越小越好

  • 对于 \(\text{delta}\) 为负的二元组,一定比 \(\text{delta}\) 为正的二元组优。
  • 对于 \(\text{delta}\) 都为负的二元组,\(\text{mx}\) 越小的优先级越高。
  • 对于 \(\text{delta}\) 都为正的二元组,\(\text{delta−mx}\) 越小的优先级越高。

然后我们就证明了不存在二元组 \(A,B,C\) 满足 \(A<B,B<C,C<A\),即可以按照这个优先级给二元组排序,且这样最小化最终的 \(mx\)

这样就解决了简化问题。

考虑原问题。

\(u\) 的所有儿子 \(v\) 都执行过操作才能对 \(u\) 执行操作 这个限制非常麻烦,考虑反转:

对每个点 \(u\) 可以执行一次操作:

  • \(\text{step1.}\)\(\sum_{v\in son(u)}w[v]\) 接到序列 \(c\) 的最后面
  • \(\text{step2.}\)\(-w[u]\) 接到序列 \(c\) 的最后面

\(u\) 的父亲 \(f\) 执行过操作才能对 \(u\) 执行操作。

问序列 \(c\) 后缀和的最大值最小可以是多少。
(反转前相当于每个操作先把 \(w[u]\) 接到 \(c\) 的最后面,再把 \(-\sum w[v]\) 接到 \(c\) 的最后面,求最终的 \(c\) 序列的前缀和的最大值最小可以是多少)

转化后 \(u\) 点的操作对应的二元组为 \((\sum w[v]-w[u],\sum w[v])\)

观察发现,任意两个二元组合并以后优先级至少比之前的一个二元组优先级低,所以对于某一时刻优先级最高的二元组,就算其父亲还没有放,其也会在其父亲被放之后马上被放,所以可以直接将这个二元组与它的父亲合并。那么用一个堆维护一下当前优先级最高的二元组,就得到了根的答案的操作序列。

最后有一个结论,对于任意一个节点,其答案的操作序列一定是根的操作序列的一个子序列,这里不太需要证明,直接从优先级的角度考虑就好了,所以用线段树合并维护每一个子树里的操作序列和结合后的答案即可,总复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n,par[200005],fa[200005];
int vis[200005],b[200005],c[200005],tim;
int rt[200005];
long long w[200005],sw[200005],ans[200005];
vector<int> g[200005],vec[200005];
struct node{
	long long delta,mx;int id;
	node(){}
	node(long long _delta,long long _mx,int _id){
		delta=_delta;mx=_mx;id=_id;
	}
	inline node operator +(node b){
		return node(delta+b.delta,max(mx,delta+b.mx),id);
	}
	inline bool operator <(const node &b)const{
		long long tmp1=max(mx,delta+b.mx);
		long long tmp2=max(b.mx,b.delta+mx);
		if(tmp1!=tmp2)return tmp1<tmp2;
		if(delta!=b.delta)return delta<b.delta;
		if(mx!=b.mx)return mx<b.mx;
		return id<b.id;
	}
}a[200005];
set<node> s;
int find(int x){
	if(x==fa[x])return x;
	return fa[x]=find(fa[x]);
}
void init(int x){
	vis[x]=1;
	b[x]=++tim;c[tim]=x;
	for(auto u:vec[x])init(u);
}
namespace Seg{
	int ls[6000005],rs[6000005],tot;
	node tr[6000005];
	inline void pushup(int u){
		if(!ls[u]){tr[u]=tr[rs[u]];return;}
		if(!rs[u]){tr[u]=tr[ls[u]];return;}
		tr[u]=tr[ls[u]]+tr[rs[u]];
	}
	void insert(int &u,int l,int r,int pos){
		if(!u)u=++tot;
		if(l==r){
			tr[u]=a[l];
			return;
		}
		int mid=(l+r)>>1;
		if(pos<=mid)insert(ls[u],l,mid,pos);
		else insert(rs[u],mid+1,r,pos);
		pushup(u);
	}
	int merge(int x,int y){
		if(!x||!y)return x+y;
		ls[x]=merge(ls[x],ls[y]);
		rs[x]=merge(rs[x],rs[y]);
		pushup(x);
		return x;
	}
}
void dfs(int x){
	Seg::insert(rt[x],1,n,b[x]);
	for(auto u:g[x]){
		dfs(u);
		rt[x]=Seg::merge(rt[x],rt[u]);
	}
	ans[x]=Seg::tr[rt[x]].mx;
}
int main(){
	scanf("%d%d",&T,&n);
	for(int i=2;i<=n;i++){
		scanf("%d",&par[i]);
		g[par[i]].push_back(i);
	}
	for(int i=1;i<=n;i++){
		scanf("%lld",&w[i]);
		sw[par[i]]+=w[i];fa[i]=i;
	}
	for(int i=1;i<=n;i++){
		a[i]=node(sw[i]-w[i],sw[i],i);
		s.insert(a[i]);
	}
	for(int i=1;i<=n;i++){
		node now=*(s.begin()); 
		s.erase(s.begin());
		int x=now.id;
		if(x==1||vis[par[x]])init(x);
		else{
			int fx=find(par[x]);
			s.erase(a[fx]);
			a[fx]=a[fx]+a[x];
			s.insert(a[fx]);
			vec[fx].push_back(x),fa[x]=fx;
		}
	}
	for(int i=1;i<=n;i++){
		a[b[i]]=node(sw[i]-w[i],sw[i],i);
	}
	dfs(1);
	for(int i=1;i<=n;i++)printf("%lld ",max(w[i],ans[i]+w[i]));



	return 0;
}
posted @ 2022-06-23 12:06  一粒夸克  阅读(93)  评论(0编辑  收藏  举报