Codeforces Round #339 (Div. 1) D. Kingdom and its Cities 虚树 + dp

传送门

文章目录

题意:

在这里插入图片描述
n ≤ 1 e 5 , ∑ k ≤ 1 e 5 , q ≤ 1 e 5 n\le1e5,\sum k\le1e5,q\le1e5 n1e5,k1e5,q1e5

思路:

经过分析,外敌占领的城市只有可能是两点的 l c a lca lca或者两点之间任意一点,是哪个点无所谓,所以我们首先检查一下是否存在两个重要城市互为父子,否则的话一定有解。
由于存在大量不重要的点,所以我们按照重要的城市建立一颗虚树,让后在虚树上 d p dp dp,每个点分两种情况:
( 1 ) (1) (1)这个点是重要城市,那么他必须与所有有重要城市的儿子之间断开。
( 2 ) (2) (2)这个点不是重要城市,那么如果儿子也没有重要城市就对答案无贡献。如果儿子有一个重要城市,那么将这个城市也变成重要城市即可,相当于标记上传了,对答案也无贡献。如果儿子有多于 1 1 1个重要城市,那么只需要将当前这个点占领即可,答案加一。

最后不要忘记清空数据,清空的时候可以再遍历一次清空即可。

// Problem: D. Kingdom and its Cities
// Contest: Codeforces - Codeforces Round #339 (Div. 1)
// URL: https://codeforces.com/problemset/problem/613/D
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
//#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<cmath>
#include<cctype>
#include<vector>
#include<set>
#include<queue>
#include<algorithm>
#include<sstream>
#include<ctime>
#include<cstdlib>
#define X first
#define Y second
#define L (u<<1)
#define R (u<<1|1)
#define pb push_back
#define mk make_pair
#define Mid (tr[u].l+tr[u].r>>1)
#define Len(u) (tr[u].r-tr[u].l+1)
#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define db puts("---")
using namespace std;

//void rd_cre() { freopen("d://dp//data.txt","w",stdout); srand(time(NULL)); }
//void rd_ac() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//AC.txt","w",stdout); }
//void rd_wa() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//WA.txt","w",stdout); }

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;

const int N=1000010,mod=1e9+7,INF=0x3f3f3f3f;
const double eps=1e-6;

int n,m;
vector<int>v[N];
int a[N],tot;
int stk[N],top;
int dfn[N],idx,fa[N][20],depth[N];
int has[N],ans;

void dfs1(int u,int f) {
	fa[u][0]=f; dfn[u]=++idx;
	depth[u]=depth[f]+1;
	for(int i=1;i<=18;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
	for(auto x:v[u]) {
		if(x==f) continue;
		dfs1(x,u);
	}
}

int lca(int a,int b) {
	if(depth[a]<depth[b]) swap(a,b);
	for(int i=18;i>=0;i--) 
		if(depth[fa[a][i]]>=depth[b]) 
			a=fa[a][i];
	if(a==b) return a;
	for(int i=18;i>=0;i--)
		if(fa[a][i]!=fa[b][i]) 
			a=fa[a][i],b=fa[b][i];
	return fa[a][0];
}

bool check() {
	for(int i=1;i<=tot;i++) {
		if(has[fa[a[i]][0]]) return true;
	}
	return false;
}

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

void insert(int x) {
	if(!top) stk[++top]=x;
	else {
		int father=lca(x,stk[top]);
		while(top>1&&depth[father]<depth[stk[top-1]]) {
			v[stk[top-1]].pb(stk[top]),top--;
		}
		if(depth[father]<depth[stk[top]]) v[father].pb(stk[top--]);
		if(!top||(stk[top]!=father)) stk[++top]=father;
		stk[++top]=x;
	}
}

void dfs2(int u,int f) {
	if(has[u]) {
		int add=0;
		for(auto x:v[u]) {
			if(x==f) continue;
			dfs2(x,u);
			add+=has[x]>0;
		}
		ans+=add;
	} else {
		int add=0;
		for(auto x:v[u]) {
			if(x==f) continue;
			dfs2(x,u);
			add+=has[x]>0;
		}
		if(add>1) ans++;
		else if(add==1) has[u]=1;
	}
}

void dfs3(int u,int f) {
	//printf("%d**\n",u);
	has[u]=0;
	for(auto x:v[u]) {
		if(x==f) continue;
		dfs3(x,u);
	}
	v[u].clear();
}

int main()
{
//	ios::sync_with_stdio(false);
//	cin.tie(0);
	
	scanf("%d",&n);
	for(int i=1;i<=n-1;i++) {
		int a,b; scanf("%d%d",&a,&b);
		v[b].pb(a); v[a].pb(b);
	}
	dfs1(1,0);
	for(int i=1;i<=n;i++) v[i].clear();
	scanf("%d",&m);
	while(m--) {
		scanf("%d",&tot);
		for(int i=1;i<=tot;i++) scanf("%d",&a[i]),has[a[i]]=1;
		if(check()) {
			for(int i=1;i<=tot;i++) has[a[i]]=0;
			puts("-1");
			continue;
		}
		sort(a+1,a+1+tot,cmp); top=0; ans=0;
		if(a[1]!=1) stk[++top]=1;
		for(int i=1;i<=tot;i++) insert(a[i]);
		while(top>1) v[stk[top-1]].pb(stk[top]),top--; 
		dfs2(1,0); dfs3(1,0);
		printf("%d\n",ans);
	}
	



	return 0;
}
/*

*/









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值