【NOI2021 D1T1】 轻重边(树链剖分)

【NOI2021 D1T1】 轻重边

前言

惨 hycj 惨

can

话说赛后有很多大佬评价这道题。

@ix35:近五年 NOI 最难的 D1T1。

@绝顶我为峰:也许是近几年最简单的 NOI 题。

@wangrx:这道题就差没把 “LCT” 写到题面中去了。

确实。

解题过程&思路

先说一下我最初的想法。

最初の想法

我们不难发现对于一个点 \(u\)​​,与它相连的边最多只有两条是重边。由此我有了第一个思路。

我们可以先对树进行树链剖分,然后开两颗线段树。一颗记录重链上每条边的边权(若被输入赋为重边为 \(1\),否则为 \(0\));另一颗记录对于每个节点与它相连的边中,不是剖分出的重边,但是被输入赋为重边的边的数量是多少(有点绕)。我们再开一个数组记录一下这些边指向那个节点。

看上去很可做对吧,怎么做大家可以自己想。

具体我先不说(这句话懂得都懂)。

至今卡在 \(45pts\)​ 过不去。😦

正确の想法

线段树记录边权这件事情还是太复杂了,我们想想怎么把边的问题转化到点上。

我们可以这样想,对于每个点假若我们有一个颜色,每次对于一条链进行修改时,我们就这条链上所有点颜色改成一致的颜色。最后统计答案,只需要看链上有多少相邻的点颜色为相同即可。这个可以用简单的线段树处理解决,记录一下每个区间的左端点颜色,右端点颜色和相邻点对颜色相同的个数。具体操作见代码 pushup 函数即可。

其他操作就是和普通的树链剖分一样了。

其他问题看代码都能解决(大概)。

时间复杂度 \(O(n\log n)\)​。

代码

//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
using namespace std;

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

const int MAXN=1e5+10;
const int henhenhenaaa=114514;
//别在意这个臭数字
//这是为了让所有区间和 s[0] 合并都不变

int n,q,m,root,mod,cnt,time_cnt,col_cnt;
int h[MAXN],dep[MAXN],top[MAXN],dfn[MAXN],son[MAXN],size[MAXN],father[MAXN],idx[MAXN],last[MAXN],dir[MAXN][3];
struct edge {
	int to,nxt;
}e[MAXN<<1];
struct segment {
	int lcol,rcol,num;
	int lazy;
}s[MAXN<<2];

void addedge(int u,int v) {
	e[++cnt].to=v;
	e[cnt].nxt=h[u];
	h[u]=cnt;
}
void insert(int u,int v) {
	addedge(u,v);
	addedge(v,u);
}

void dfs1(int u,int fa) {
	size[u]=1;
	top[u]=u;
	father[u]=fa;
	son[u]=0;
	for(int i=h[u];i;i=e[i].nxt) {
		int v=e[i].to;
		
		if(v!=fa) {
			dep[v]=dep[u]+1;
			dfs1(v,u);
			size[u]+=size[v];
			if(size[v]>size[son[u]]) {
				son[u]=v;
			}
		}
	}
}
void dfs2(int u,int fa) {
	dfn[u]=++time_cnt;
	idx[dfn[u]]=u;
	last[u]=dfn[u];
	if(son[u]) {
		top[son[u]]=top[u];
		dfs2(son[u],u);
		last[u]=max(last[u],last[son[u]]);
	}
	for(int i=h[u];i;i=e[i].nxt) {
		int v=e[i].to;
		
		if(v!=fa&&v!=son[u]) {
			dfs2(v,u);
			last[u]=max(last[u],last[v]);
		}
	}
}

void pushdown(int p,int l,int r) {
	if(l==r) {
		return ;
	}
	int mid=(l+r)>>1;
	if(s[p].lazy) {
		s[p<<1].lcol=s[p<<1].rcol=s[p].lazy;
		s[p<<1].num=mid-l;
		s[p<<1|1].lcol=s[p<<1|1].rcol=s[p].lazy;
		s[p<<1|1].num=r-mid-1;
		s[p<<1].lazy=s[p<<1|1].lazy=s[p].lazy;
		s[p].lazy=0;
	}
}
segment rev(segment x) {
    //翻转一个区间
	segment ret;
	ret.lazy=0;
	ret.lcol=x.rcol;
	ret.rcol=x.lcol;
	ret.num=x.num;
	return ret;
}
segment pushup(segment lft,segment rgt) {
    //所有区间和 s[0] 合并都不变
	if(lft.lazy==-henhenhenaaa) {
		return rgt;
	}
	if(rgt.lazy==-henhenhenaaa) {
		return lft;
	}
	segment ret;
	ret.lazy=0;
	ret.lcol=lft.lcol;
	ret.rcol=rgt.rcol;
	ret.num=lft.num+rgt.num+(lft.rcol==rgt.lcol? 1:0);
	return ret;
}
void build(int l,int r,int p) {
	if(l==r) {
		s[p].lazy=0;
		s[p].lcol=-l;
		s[p].rcol=-r;
		s[p].num=0;
		return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,p<<1);
	build(mid+1,r,p<<1|1);
	s[p]=pushup(s[p<<1],s[p<<1|1]);
}
void modify(int l,int r,int p,int x,int y,int val) {
	if(r<x||y<l) {
		return ;
	}
	pushdown(p,l,r);
	if(x<=l&&r<=y) {
		s[p].lazy=val;
		s[p].lcol=s[p].rcol=val;
		s[p].num=r-l;
		return ;
	}
	int mid=(l+r)>>1;
	modify(l,mid,p<<1,x,y,val);
	modify(mid+1,r,p<<1|1,x,y,val);
	s[p]=pushup(s[p<<1],s[p<<1|1]);
}
segment query(int l,int r,int p,int x,int y) {
	if(r<x||y<l) {
		return s[0];
	}
	pushdown(p,l,r);
	if(x<=l&&r<=y) {
		return s[p];
	}
	int mid=(l+r)>>1;
	return pushup(query(l,mid,p<<1,x,y),query(mid+1,r,p<<1|1,x,y));
}

void real_modify(int u,int v) {
	col_cnt++;
	while(top[u]!=top[v]) {
		if(dep[top[u]]<dep[top[v]]) {
			swap(u,v);
		}
		modify(1,n,1,dfn[top[u]],dfn[u],col_cnt);
		u=father[top[u]];
	}
	if(dep[u]<dep[v]) {
		swap(u,v);
	}
	modify(1,n,1,dfn[v],dfn[u],col_cnt);
}
int real_query(int u,int v) {
	int ans=0;
	bool tp=0;
	segment lft=s[0],rgt=s[0];
	while(top[u]!=top[v]) {
		if(dep[top[u]]<dep[top[v]]) {
			swap(u,v);
			tp=!tp;
		}
		if(tp) {
			rgt=pushup(rgt,rev(query(1,n,1,dfn[top[u]],dfn[u])));
		}
		else {
			lft=pushup(lft,rev(query(1,n,1,dfn[top[u]],dfn[u])));
		}
		u=father[top[u]];
	}
	if(dep[u]<dep[v]) {
		swap(u,v);
		tp=!tp;
	}
	if(tp) {
		lft=pushup(lft,query(1,n,1,dfn[v],dfn[u]));
	}
	else {
		lft=pushup(lft,rev(query(1,n,1,dfn[v],dfn[u])));
	}
	return pushup(lft,rev(rgt)).num;
    //合并链信息时要注意链的方向
}

signed main() {
	freopen("edge.in","r",stdin);
	freopen("edge.out","w",stdout);
	
    //所有区间和 s[0] 合并都不变
	s[0].lazy=-henhenhenaaa;
	int t=read();
	//t=1;
	while(t--) {
		cin>>n>>q;
		cnt=time_cnt=col_cnt=0;
		fill(h+1,h+n+1,0);
        //清零做到位
		for(int i=1;i<n;i++) {
			int u,v;
			u=read();v=read();
			insert(u,v);
		}
		dfs1(1,0);
		dfs2(1,0);
		build(1,n,1);
		
		while(q--) {
			int tp,a,b;
			tp=read();a=read();b=read();
			if(tp==1) {
				real_modify(a,b);
			}
			else {
				printf("%d\n",real_query(a,b));
			}
		}
	}

	fclose(stdin);
	fclose(stdout);
	return 0;
}


另外の想法

貌似可以用 LCT 解决。

我没学过,大家可以参考别的博主的博客学习一下。

posted @ 2021-08-06 18:12  huayucaiji  阅读(104)  评论(0编辑  收藏  举报