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,所以说如果一道题做不出来,偶尔暴力赌一把也挺好的。。。