1. 程式人生 > 實用技巧 >CF1328E Tree Queries

CF1328E Tree Queries

題意簡述

題目連結

  給定一棵有根樹,每次給定k個節點,詢問是否存在一條以根節點為一端的鏈,使得這k個節點到這條鏈的距離均<=1(只需判斷可行性,無需給出方案)。

演算法概述

  思維題一般都需要我們分析出一些題目的性質。

  這道題最特殊的點顯然在於其要求的距離小於等於1,也就是說,這k個點要麼在鏈上,要麼在鏈旁。手動畫一畫圖可以發現,對於這兩種情況,都滿足這個點的父親必然在鏈上

  那麼問題就轉化為了給定k個點,詢問這k個點是否均在某條以根節點為一端的鏈上。由於鏈的數量可能很多,而且最重要的是鏈之間會有交集,故我們無法直接處理出每個點所在的鏈,所以我們需要考慮其他做法。

  我們可以對給定的k個點按照深度排序,然後依次判斷每個點是否在其前一個點的子樹中即可。

  子樹很好判斷,根據dfs序,節點v在節點u的子樹中,當且僅當dfn[u]<=dfn[v]<=dfn[u]+size[u]-1

參考程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=2e5+10;
struct Edge{
	int to,next;
}edge[N<<1];int idx;
int h[N];

void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}

int siz[N],dep[N],id[N],fa[N],q[N];
int timestamp;
int n,m,k;

void dfs(int p,int f)
{
	id[p]=++timestamp;
	siz[p]=1;
	fa[p]=f;
	dep[p]=dep[f]+1;
	for(int i=h[p];~i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(to==f)continue;
		dfs(to,p);
		siz[p]+=siz[to];
	}
}

bool cmp(int a,int b)
{
	return dep[a]<dep[b];
}

bool check()
{
	for(int i=1;i<=k-1;i++)
	{
		int u=q[i],v=q[i+1];
		if(id[u]<=id[v]&&id[v]<=id[u]+siz[u]-1)continue;
		return false;
	}
	return true;
}

int main()
{
	memset(h,-1,sizeof h);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-1;i++)
	{
		int u,v;scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,1);
	while(m--)
	{
		scanf("%d",&k);
		for(int i=1;i<=k;i++)
		{
			int x;scanf("%d",&x); 
			q[i]=fa[x];
		}
		sort(q+1,q+k+1,cmp);
		if(check())puts("YES");
		else puts("NO");
	}
	return 0;
}