JZOJ4811【排队】

【题目大意】

对于给定的一棵树(初始全为白点),执行两种询问

1.每次询问给这棵树填充x个黑点,填充规则:每个黑点从根节点(1)往叶子节点走(只能走白点)(如果有多个可行路径,则走到比编号最小的子节点),直到不能走为止,最后停留的位置被染为黑色,并输出第x个黑点最后停留的位置

2.将x位置的黑点染为白色,其余黑点按照1规则重新填充,问有多少个黑点改变了位置

题目保证询问合法

【题解】

首先询问2应该比较好想到,每个黑点只会影响到是他的祖先的黑点(自行yy),所以只需树上倍增,每次询问找到x往上走最后一个黑点祖先j,ans=d[j]-d[x](d为深度)并把j染为白色

现在思考询问1,对于询问1不难想到,每个点都有自己的优先级(按照规则自行yy),一个点要填充时当且仅当所有优先级比它低的点都已被填充,所以就可以用一个堆维护剩余未填充点的优先级,每次填充堆顶的节点

当然我是用一个比较迷幻的dfs,不难想到,若要填充一个节点,当且仅当他的所有儿子节点都被填满,以及他的编号比他小的兄弟都被填满,所以就可以用一个dfs实现填充(填充前先给每个点连出的边排序,为了使得编号小的儿子节点被更早访问),比较zz的填充方法就是每次从节点1往下dfs遇到不能染色的点就不进入,当一个点所有的儿子节点都填满时在给他染色(我最后就是这样打,虽然我打的那个是我接下来要说的正确的dfs),不难想到,这个方法随便出个数据就可以卡掉了(会超时啊233)(虽然并没存在这种数据),然后在深入往下思考,可以dfs时会把许多一万年都不会被染色的点遍历一万次(比如根节点),所以我们可以改变一下dfs的顺序,每次从优先级最高的白点开始dfs,然后进行普通的dfs,再加上一个模拟回溯的过程,就是记录当前这个节点是不是由它的父亲走过来的(因为我们dfs的第一个节点并不是根节点),如果不是(比如他可能从它的儿子走过来)就dfs它的父亲节点(这里就是从儿子走到父亲),这样就可以实现一个模拟回溯的过程。

总之讲的有点迷幻(我也觉得好迷幻)

详见代码

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
#include <ctime>
#include <vector>
using namespace std;
int i,j,k,l,m,n,t,s,x,y;
vector <int> road[100005];
int f[100005][22],col[100005],d[100005],child[100005];
void build(int u)
  {
  	int i,v;
  	for (i=0;i<road[u].size();i++)
  	  {
		v=road[u][i];
		if (d[v]) continue;
		d[v]=d[u]+1;f[v][0]=u;
		build(v);
	  }
  }
void dfs(int u,int fa)
  {
  	int i,v,num=0;
  	if (x==0) return;
  	for (i=0;i<road[u].size();i++)
  	  {
  	  	v=road[u][i];
	    if (v!=f[u][0]) num++;
  	  	if (col[v]||v==f[u][0]) continue;
  	  	dfs(v,u);
  	  	if (x==0) return;
	  }
	if (num==child[u]) col[u]=1,x--,s=u;
	if (u==1) return;
	if (fa!=f[u][0]) dfs(f[u][0],u);
  }
void del(int x)
  {
  	int i,j=x;
  	for (;col[f[x][0]]!=0;)
  	  {
  	  	for (i=1;col[f[x][i]]!=0;i++);i--;
  	  	x=f[x][i];
	  }
	col[x]=0;
	printf("%d\n",d[j]-d[x]);
	s=x;
  }
int main()
  {
  	scanf("%d%d",&n,&m);
  	for (i=1;i<n;i++) 
	  scanf("%d%d",&x,&y),road[x].push_back(y),road[y].push_back(x);
  	for (i=1;i<=n;i++) 
	  sort(road[i].begin(),road[i].end());
    f[1][0]=0;d[1]=1;
	build(1);
	for (i=1;i<=20;i++)
	  for (j=1;j<=n;j++) 
	    f[j][i]=f[f[j][i-1]][i-1];
	//memset(col,-1,sizeof col);
	for (i=2;i<=n;i++) child[f[i][0]]++;
	s=1;
	for (i=1;i<=m;i++)
	  {
	  	if (i==1501) 
	  	  {
	  	  	i=1501;
			}
	  	scanf("%d%d",&y,&x);
	  	if (y==1) 
		  {
		   dfs(1,0);
		   printf("%d\n",s);s=f[s][0];
	      }
	  	if (y==2) del(x);
	  }
  }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值