HDU-3974 Assign the task 线段树 或 直接模拟多叉树 或 并查集 (三种方法)

题目大意

t 组数据(t<=10),每组第一行一个 n 表示 n 个员工(n<=5e4),接下来 n-1 行,每行两个整数 u,v 表示 v 是 u 的上司

然后一行 m 表示有 m 个操作(m<=5e4) , 接下来 m 行 由一个字符 和 整型数构成 ,表示不同操作

C x 表示 输出 第 x 个员工的工作号 

T x y 表示 将 第 x 个员工的工作号变为 y (x<=n,0<=y<=1e9)

规定,改变工作时,被改变员工的下属一同变为 y

解析

通读题干,发现直接给了个多叉树,于是法一我们先讲直接模拟

法一:直接模拟多叉树

首先,读者可以画个树,自行标号dfs序,体会下dfs序的特点

不难发现,对于任意子树所含的结点编号是连续的

且该子树所含结点最小编号就是根结点编号

然后这个dfs序就是我们解决这道题的关键所在

按题意直接建好多叉树,标好dfs序,然后你会发现其实操作蛮简单的

查询员工,变成一层层去找对应子树根结点

更改员工,找到对应子树根结点挂上lazy就完事了

相当简单,不多说了,直接挂代码了

#pragma GCC optimize(2)
#include 
using namespace std;
struct node
{
	int l,r,job,lazy;
	vector son;
}tree[50005];
int t,n,m,cnt,root,u,v,x,y;
char c;
bool notroot[50005];
void dfsbuild(int rt)
{
	tree[rt].job=tree[rt].lazy=-1;
	tree[rt].l=++cnt;
	for(auto x:tree[rt].son) dfsbuild(x);
	tree[rt].r=cnt;
}
void down(int rt)
{
	if(tree[rt].lazy==-1) return;
	for(auto s:tree[rt].son) tree[s].job=tree[s].lazy=tree[rt].lazy;
	tree[rt].lazy=-1;
}
void update(int x,int y,int rt)
{
	if(x==rt)
	{
		tree[rt].job=tree[rt].lazy=y;
		return;
	}
	down(rt);
	for(auto s:tree[rt].son) if(tree[s].l<=tree[x].l&&tree[x].l<=tree[s].r) update(x,y,s);
}
int query(int x,int rt)
{
	if(x==rt) return tree[rt].job;
	down(rt);
	for(auto s:tree[rt].son) if(tree[s].l<=tree[x].l&&tree[x].l<=tree[s].r) return query(x,s);
}
int main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>t;
	for(int cas=1;cas<=t;cas++)
	{
		cin>>n,cnt=0;
		for(int i=1;i<=n;i++) tree[i].son.clear(),notroot[i]=0;
		for(int i=1;i>u>>v,tree[v].son.push_back(u),notroot[u]=1;
		for(int i=1;i<=n;i++) if(!notroot[i]) root=i;
		dfsbuild(root);
		cin>>m;
		cout<<"Case #"<>c>>x;
			if(c=='C') cout<>y,update(x,y,root);
		}
	}
	return 0;
}

法二:线段树

其实这个题线段树和多叉树做起来还蛮像的,但是其实截然不同,操作也很不相同

然后dfs序还是本题关键

首先打好dfs序,原多叉树每个子树的根结点就会对应一段区间(最小编号到最大编号)

然后把多叉树直接扔掉不要管了,直接处理得到的区间(就像是把多叉树直接拍扁成一个区间)

你就会发现,什么多叉树,就是个变相考你区间染色,和多叉树直接没了关系

查询一个点,就是查其单点编号

更改一个点,就是改一段对应区间

线段树和之前的树没啥关系,只是为了实现区间操作的功能而建立的,不要混乱了

其实这个方法只要理清思路不混乱就没啥了,直接上代码了

#pragma GCC optimize(2)
#include 
using namespace std;
int t,n,m,tree[50005<<2],lid[50005],rid[50005],cnt,u,v,x,y;
bool notroot[50005];
char c;
vector g[50005];
void initbuild(int l,int r,int rt)
{
    tree[rt]=-1;
    if(l==r) return;
    initbuild(l,(l+r)/2,rt<<1);
    initbuild((l+r)/2+1,r,rt<<1|1);
}
void dfs(int rt)
{
    lid[rt]=++cnt;
    for(auto s:g[rt]) dfs(s);
    rid[rt]=cnt;
}
void down(int rt)
{
	if(tree[rt]==-1) return;
	tree[rt<<1]=tree[rt<<1|1]=tree[rt];
	tree[rt]=-1;
}
void update(int a,int b,int c,int l,int r,int rt)
{
	if(rb) return;
	if(a<=l&&r<=b)
	{
		tree[rt]=c;
		return;
	}
	down(rt);
	update(a,b,c,l,(l+r)/2,rt<<1);
	update(a,b,c,(l+r)/2+1,r,rt<<1|1);
}
int query(int x,int l,int r,int rt)
{
	if(l==r) return tree[rt];
	down(rt);
	if(x<=(l+r)/2) return query(x,l,(l+r)/2,rt<<1);
	return query(x,(l+r)/2+1,r,rt<<1|1);
}
int main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>t;
	for(int cas=1;cas<=t;cas++)
	{
		cin>>n,cnt=0;
		initbuild(1,n,1);
		for(int i=1;i<=n;i++) g[i].clear(),notroot[i]=0;
		for(int i=1;i>u>>v,g[v].push_back(u),notroot[u]=1;
		for(int i=1;i<=n;i++) if(!notroot[i]) dfs(i);
		cin>>m;
		cout<<"Case #"<>c>>x;
			if(c=='C') cout<>y,update(lid[x],rid[x],y,1,n,1);
		}
	}
	return 0;
}

法三:并查集

说来惭愧,这个方法我实在没法起名

说他是并查集吧,其实并查集的find union操作都没用上

形式上似乎是并查集,但并没有使用任何并查集的功能,应该说和并查集半点关系没有吧

实在不知道该怎么说,算了,不管这个了

总之这个方法就是直接做,也算不上模拟什么的,反正就是直接从逻辑角度就把答案推出来了,非常巧妙

记录每个结点分到的工作与时间,并且不向下更新

查询的时候一路找爸爸就可以了,时间最靠后的那个任务就是当前儿子的任务

思路过于简单了,不多说废话了

#pragma GCC optimize(2)
#include 
using namespace std;
struct node
{
	int job,time;
}a[50005];
int fa[50005],t,n,m,u,v,x,y,cnt,tmp;
char c;
int main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>t;
	for(int cas=1;cas<=t;cas++)
	{
		cin>>n,cnt=0;
		for(int i=1;i<=n;i++) fa[i]=a[i].job=a[i].time=-1;
		for(int i=1;i>u>>v,fa[u]=v;
		cin>>m;
		cout<<"Case #"<>c>>x;
			if(c=='C')
			{
				y=a[x].job;
				tmp=a[x].time;
				while(x!=-1) 
				{
					if(a[x].time>tmp) y=a[x].job,tmp=a[x].time;
					x=fa[x];
				}
				cout<>y,a[x].job=y,a[x].time=++cnt;
		}
	}
	return 0;
}

这个方法是我看了别人的题解后写的

然后我就在想,哇,这么简单我怎么没想到呢

然后我发现不对劲,我怎么可能没想到?

这不是最坏情况O(mn)会超时的吗。。当时肯定直接pass掉了啊。。

为啥就能A掉了,果然数据比较弱,没有那种链的情况。。

真滴服。。

不得不说暴力大法好。。。

如果比赛遇到这种情况就爽死了QAQ,所以说如果一道题做不出来,偶尔暴力赌一把也挺好的。。。

你可能感兴趣的:(HDU-3974 Assign the task 线段树 或 直接模拟多叉树 或 并查集 (三种方法))