【XSY4206】QWQ(trick)

两个问题的解决方法感觉很妙:

一、

给你若干棵树 T 1 , T 2 , ⋯   , T k T_1,T_2,\cdots,T_k T1,T2,,Tk,设 f ( T i , u , v ) f(T_i,u,v) f(Ti,u,v) 为树 T T T l c a ( u , v ) lca(u,v) lca(u,v) 的深度,问如何优美地表示 g ( u , v ) = min ⁡ i = 1 k f ( T i , u , v ) g(u,v)=\min_{i=1}^k f(T_i,u,v) g(u,v)=mini=1kf(Ti,u,v)

其实很简单,设 P u P_u Pu 为一个 k k k 元组序列,设第 d d d 位为 ( a d , 1 , a d , 2 , ⋯   , a d , k ) (a_{d,1},a_{d,2},\cdots,a_{d,k}) (ad,1,ad,2,,ad,k),那么 a d , i a_{d,i} ad,i 就表示在树 T i T_i Ti 中从根到 u u u 路径上深度为 d d d 的那个节点(若不存在就设为一个不可能和其他任何值相等的值)。然后把 P 1 , ⋯   , P n P_1,\cdots,P_n P1,,Pn 插入一棵 Trie 树中,那么 g ( u , v ) g(u,v) g(u,v) 就是 Trie 树中 P u P_u Pu P v P_v Pv 的 lca 的深度(即 P u P_u Pu P v P_v Pv 的最长公共前缀)。

那么直接维护 P 1 , ⋯   , P n P_1,\cdots,P_n P1,,Pn 这些节点在 Trie 树上的虚树即可。构建过程中需要用到比较 dfn、查询深度、查询 lca,这些都可以推出来。

二、

给你树上若干个关键点 p 1 , ⋯   , p k p_1,\cdots,p_k p1,,pk,保证 k k k 为偶数,要求将它们两两配对,使得每对点的距离之和最小。支持动态插入、删除关键点和查询。

首先按 dfn 序排序然后相邻两两配对的思路是错的,因为不同的递归儿子顺序会导致不同的 dfn 序,从而配对结果不同,不一定能找到最优解。

事实上答案等价于有多少条边两侧各有奇数个关键点,因为若全为奇数,则一定有至少一组配对若会经过这条边,而且能构造出来仅有一组配对会经过这条边;若全为偶数,则一定能构造出来不经过这条边的方案。(不可能一奇一偶,因为保证询问时 k k k 为偶数)

那么也就是问有多少个点的 s i z e size size 是奇数,使用树剖维护即可。

原题代码:

#include<bits/stdc++.h>

#define LN 18
#define N 200010
#define ll long long
#define fi first
#define se second
#define pii pair<int,int>
#define mk(a,b) make_pair(a,b)

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n;
pii pos[N];

struct Tree
{
	int cnt,head[N],nxt[N],to[N];
	int idx,dfn[N],d[N],fa[N][LN];
	void adde(int u,int v)
	{
		to[++cnt]=v;
		nxt[cnt]=head[u];
		head[u]=cnt;
	}
	void dfs(int u)
	{
		dfn[u]=++idx;
		for(int i=1;i<=17;i++)
			fa[u][i]=fa[fa[u][i-1]][i-1];
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa[u][0]) continue;
			d[v]=d[u]+1,fa[v][0]=u;
			dfs(v);
		}
	}
	void init()
	{
		d[1]=1;
		dfs(1);
	}
	inline int jump(int a,int dep)
	{
		if(d[a]==dep) return a;
		for(int i=17;i>=0;i--)
			if(d[fa[a][i]]>=dep)
				a=fa[a][i];
		return a;
	}
	inline int getlca(int a,int b)
	{
		if(d[a]<d[b]) swap(a,b);
		a=jump(a,d[b]);
		if(a==b) return a;
		for(int i=17;i>=0;i--)
			if(fa[a][i]!=fa[b][i])
				a=fa[a][i],b=fa[b][i];
		return fa[a][0];
	}
}t1,t2;

namespace Ftree
{
	const int V=N<<1;
	map<pii,int>mp;
	int node;
	int rt,idx,fa[V],d[V],size[V],son[V],top[V],id[V],rk[V];
	int cnt,head[V],to[V],nxt[V],from[V];
	void adde(pii a,pii b,int dis)
	{
		if(!mp[a]) mp[a]=++node;
		if(!mp[b]) mp[b]=++node;
		int u=mp[a],v=mp[b];
		to[++cnt]=v;
		from[v]=dis;
		nxt[cnt]=head[u];
		head[u]=cnt;
	}
	void dfs(int u)
	{
		size[u]=1;
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa[u]) continue;
			d[v]=d[u]+1,fa[v]=u;
			dfs(v);
			size[u]+=size[v];
			if(size[v]>size[son[u]]) son[u]=v;
		}
	}
	void dfs1(int u,int tp)
	{
		top[u]=tp;
		rk[id[u]=++idx]=u;
		if(son[u]) dfs1(son[u],tp);
		for(int i=head[u];i;i=nxt[i])
			if(to[i]!=fa[u]&&to[i]!=son[u])
				dfs1(to[i],to[i]);
	}
	void init()
	{
		rt=mp[mk(1,1)];
		dfs(rt),dfs1(rt,rt);
	}
}

namespace Segment
{
	int sum[N<<3][2];
	bool lazy[N<<3];
	void up(int k)
	{
		sum[k][0]=sum[k<<1][0]+sum[k<<1|1][0];
		sum[k][1]=sum[k<<1][1]+sum[k<<1|1][1];
	}
	void downn(int k)
	{
		swap(sum[k][0],sum[k][1]);
		lazy[k]^=1;
	}
	void down(int k)
	{
		if(lazy[k])
		{
			downn(k<<1),downn(k<<1|1);
			lazy[k]=0;
		}
	}
	void build(int k,int l,int r)
	{
		if(l==r)
		{
			sum[k][0]=Ftree::from[Ftree::rk[l]];
			return;
		}
		int mid=(l+r)>>1;
		build(k<<1,l,mid);
		build(k<<1|1,mid+1,r);
		up(k);
	}
	void update(int k,int l,int r,int ql,int qr)
	{
		if(ql<=l&&r<=qr)
		{
			downn(k);
			return;
		}
		down(k);
		int mid=(l+r)>>1;
		if(ql<=mid) update(k<<1,l,mid,ql,qr);
		if(qr>mid) update(k<<1|1,mid+1,r,ql,qr);
		up(k);
	}
	void init(){build(1,1,Ftree::node);}
}

namespace Build
{
	inline bool cmpdfn(const pii &a,const pii &b)
	{
		const int d1=t1.d[t1.getlca(a.fi,b.fi)],d2=t2.d[t2.getlca(a.se,b.se)];
		if(d1<=d2) return t1.dfn[a.fi]<t1.dfn[b.fi];
		return t2.dfn[a.se]<t2.dfn[b.se];
	}
	inline pii getlca(const pii &a,const pii &b)
	{
		const int d1=t1.d[t1.getlca(a.fi,b.fi)],d2=t2.d[t2.getlca(a.se,b.se)];
		const int mind=min(d1,d2);
		return mk(t1.jump(a.fi,mind),t2.jump(a.se,mind));
	}
	inline int getd(const pii &now)
	{
		return t1.d[now.fi];
	}
	int top;
	pii sta[N];
	void insert(pii now)
	{
		if(!top)
		{
			sta[++top]=now;
			return;
		}
		pii lca=getlca(sta[top],now);
		while(top>1&&getd(lca)<=getd(sta[top-1]))
			Ftree::adde(sta[top-1],sta[top],getd(sta[top])-getd(sta[top-1])),top--;
		if(lca!=sta[top])
		{
			assert(getd(lca)!=getd(sta[top]));
			Ftree::adde(lca,sta[top],getd(sta[top])-getd(lca));
			sta[top]=lca;
		}
		sta[++top]=now;
	}
	void work()
	{
		static pii p[N];
		for(int i=1;i<=n;i++)
		{
			int a=i,b=i;
			if(t1.d[a]<t2.d[b]) b=t2.jump(b,t1.d[a]);
			else a=t1.jump(a,t2.d[b]);
			pos[i]=p[i]=mk(a,b);
		}
		sort(p+1,p+n+1,cmpdfn);
		for(int i=1;i<=n;i++) insert(p[i]);
		while(top>1)
			Ftree::adde(sta[top-1],sta[top],getd(sta[top])-getd(sta[top-1])),top--;
	}
}

void insert(int u)
{
	using namespace Ftree;
	using namespace Segment;
	while(u)
	{
		update(1,1,node,id[top[u]],id[u]);
		u=fa[top[u]];
	}
}

int main()
{
//	freopen("qwq4.in","r",stdin);
//	freopen("qwq4_my.out","w",stdout);
	n=read();
	for(int i=2;i<=n;i++)
		t1.adde(read(),i),t2.adde(read(),i);
	t1.init(),t2.init();
	Build::work();
	Ftree::init();
	Segment::init();
	ll sumd=0;
	for(int i=1;i<=n;i++)
	{
		int u=Ftree::mp[pos[i]];
		sumd+=Build::getd(pos[i]);
		insert(u);
		if(!(i&1))
		{
			assert(!((sumd-Segment::sum[1][1])&1));
			printf("%lld\n",(sumd-Segment::sum[1][1])/2);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值