P6118 [JOI 2019 Final]珍しい都市 题解

该文讨论了一种针对有N个顶点的树的问题,目标是找出与特定顶点距离唯一的顶点,即独特点。算法通过深度优先搜索(DFS)策略,针对不同数据范围(4%,36%,68%,100%)提出不同的复杂度解决方案,包括线性复杂度O(N)和对数复杂度O(NlogN)。文章还提供了AC代码实现。
摘要由CSDN通过智能技术生成
问题概要
  • 有一棵有 N N N 个顶点的树。

  • 使与顶点 x x x 具有唯一距离的顶点成为独特点。

  • 对于每个顶点回答独特城市的标签类型数量。

对于 4 % 4\% 4% 的数据

对每个顶点做 DFS 或 BFS。

取一个与顶点 x x x 距离最大的顶点,设置为 D D D,独特点在 ( D , x ) (D,x) (D,x) 路径上,如果有的话,对于 ( D , x ) (D,x) (D,x) 路径,找一棵在途中分支的子树,对于分支子树的最大深度,候选独特城市消失,最后剩下的顶点都是独特点。

顺便说一下,让 D 1 D1 D1 D 2 D2 D2 为树的直径的端点(距离最长的两点的对)。

对于任意顶点 x x x D 1 D1 D1 D 2 D2 D2 中至少有一个是离 x x x 最远的顶点,

从顶点 D 1 D1 D1 进行 DFS 以确定每个顶点 x x x 是否在 ( D 1 , x ) (D1,x) (D1,x) 路径上有独特点,对 D 2 D2 D2 执行相同的 DFS 以获得每个顶点的正确答案,

从每个子树的最大深度列可以看出 ( D , x ) (D,x) (D,x) 路径上的独特点,每次通过 DFS 中的一条边时,此列都会更改其后面的恒定元素数。

设此列为 A A A,问题是:

给定很多改变 A A A 的查询 (但只有最后一个元素),对于每个 p ( 1 ≤ p ≤ ∣ A ∣ ) , p − A p ≤ j < p p(1 \leq p \leq |A|),p-A_{p} \leq j<p p(1pA),pApj<p,当满足的 j j j 被禁用时,不被禁止的 j j j 的集合 ∣ A ∣ |A| A 被设置为 S S S S S S 元素对应一个独特点。

复杂度 O ( N 2 ) O(N^2) O(N2)

对于 36 % 36\% 36% 的数据

只有一种类型的顶点标签,判断是否有独特点,只需判断 S S S 是否为空。

当到达 ( D , x ) (D,x) (D,x) 路径的顶点 x x x 时,保留从其他以外生长的子树的最大深度的行(即保留 A A A 结束的那一行),

让我们称之为 A ′ A' A,可以通过比较 S S S 的最小值与从 x x x 生长的子树的最大深度来找到答案,

提前找到每个子树的最大深度,沿着边缘向下时添加到 A ′ A' A 末尾的是刚刚下降的子树的子树兄弟的最大深度。

S S S 的最小值是原集合或 ∣ A ′ ∣ + 1 |A'|+1 A+1,可以 O ( 1 ) O(1) O(1) 向下走一次,

复杂度 O ( N ) O(N) O(N)

对于 68 % 68\% 68% 的数据

所有标签都不同,只需要知道 ∣ S ∣ |S| S 的大小。

对于每个 p ( 1 ≤ p ≤ ∣ A ∣ ) p(1 \leq p \leq |A|) p(1pA) 不要使用满足 p − A p ≤ j < p p-A_{p} \leq j<p pApj<p j j j

但是,准备辅助数组 B B B,对于每个 p ( 1 ≤ p ≤ ∣ A ∣ ) , B j ← B j + 1 p(1 \leq p \leq |A|),B_{j} \gets B_{j}+1 p(1pA),BjBj+1 其中 j j j 满足 p − A p ≤ j < p p-A_{p} \leq j<p pApj<p,然后,计算 j j j 使得 B j = 0 B_{j}=0 Bj=0

A A A 的元素修改对应B的区间加法查询,如果 B B B 的区间加法和变为 0 0 0 的元素的计数可以高速完成更好。

由于 B B B 的元素总是 0 0 0 或更多,因此只需检查 min ⁡ { B } = 0 \min\{B\}=0 min{B}=0 并计算 m i n min min 的元素。

可以通过线段树来完成, O ( log ⁡ N ) O(\log N) O(logN) 更新一次,

更新是 N N N 次,所以总体 O ( N log ⁡ N ) O(N\log N) O(NlogN)

对于 100 % 100\% 100% 的数据

更改 A A A 的查询(但只有最后一个元素),当涉及到顶点 x x x 时,它持有 A ′ A' A 从顶点 x x x 返回父节点,要保存 A A A

题中有: “当你往下走时,添加到 A ′ A' A 末尾的是你刚刚下降的子树的兄弟的子树的最大深度。”

添加到 A ′ A' A 末尾的元素单调增加,当移动到 DFS 中的兄弟子树时,查看 A A A 末尾元素的变化,除了 p p p 之外的元素可能会从 S S S 中消失但不会增加,

当返回到父节点的顶点时,如果查看 A A A 末尾元素的变化, S S S 的元素可能会消失但不会增加。

由上可知, S S S 的元素增加的次数为 N N N 次,因此,减少次数可以通过 O ( N ) O(N) O(N) 来实现。

S S S 上的操作可以用堆栈表示,所以每次 O ( 1 ) O(1) O(1)

S S S 的元素增加或减少时,可以通过改变相应顶点的标签集来更新标签类型的数量。

复杂度 O ( N ) O(N) O(N)

AC code:
#include <bits/stdc++.h>
using namespace std;
using ll=int64_t;

#define int ll
#define FOR(i,a,b) for(int i=int(a);i<int(b);i++)
#define REP(i,b) FOR(i,0,b)
#define MP make_pair
#define PB push_back
#define EB emplace_back
#define ALL(x) x.begin(),x.end()
auto& errStream=cerr;
#ifdef LOCAL
#define cerr (cerr<<"-- line "<<__LINE__<<" -- ")
#else
class CerrDummy {} cerrDummy;
template<class T>
CerrDummy& operator<<(CerrDummy&cd,const T&) {return cd;}
using charTDummy=char;
using traitsDummy=char_traits<charTDummy>;
CerrDummy& operator<<(CerrDummy&cd,basic_ostream<charTDummy,traitsDummy>&(basic_ostream<charTDummy,traitsDummy>&)) {return cd;}
#define cerr cerrDummy
#endif
#define REACH cerr<<"reached"<<endl
#define DMP(x) cerr<<#x<<":"<<x<<endl
#define ZERO(x) memset(x,0,sizeof(x))
#define ONE(x) memset(x,-1,sizeof(x))

using pi=pair<int,int>;
using vi=vector<int>;
using ld=long double;

template<class T,class U>
ostream& operator<<(ostream& os,const pair<T,U>& p) {os<<"("<<p.first<<","<<p.second<<")";return os;}

template<class T>
ostream& operator <<(ostream& os,const vector<T>& v) {os<<"{";REP(i,(int)v.size()) {if(i)os<<",";os<<v[i];}os<<"}";return os;}

inline ll read() {ll i;scanf("%"  SCNd64,&i);return i;}

inline void printSpace() {printf(" ");}

inline void printEoln() {printf("\n");}

inline void print(ll x,int suc=1) {printf("%" PRId64,x);if(suc==1)printEoln();if(suc==2)printSpace();}

inline string readString() {static char buf[3341000];scanf("%s",buf);return string(buf);}

inline char* readCharArray() {
	static char buf[3341000];
	static int bufUsed=0;
	char* ret=buf+bufUsed;
	scanf("%s",ret);
	bufUsed+=strlen(ret)+1;
	return ret;
}

template<class T,class U>
inline void chmax(T& a,U b) {if(a<b)a=b;}

template<class T,class U>
inline void chmin(T& a,U b) {if(b<a)a=b;}

template<class T>
inline T Sq(const T& t) {return t*t;}

const ll infLL=LLONG_MAX/3;

#ifdef int
const int inf=infLL;
#else
const int inf=INT_MAX/2-100;
#endif

const int Nmax=200010;
vi tr[Nmax];
int ans[Nmax];
int col[Nmax],cnt[Nmax],curAns,stBuf[Nmax],stS;

inline void AddCol(int c) {if(cnt[c]==0)curAns++;cnt[c]++;}

inline void DelCol(int c) {cnt[c]--;if(cnt[c]==0)curAns--;}

inline void Push(int v) {stBuf[stS++]=v;AddCol(col[v]);}

inline bool Empty() {return stS==0;}

inline int Last() {return stBuf[stS-1];}

inline void Pop() {DelCol(col[Last()]);stS--;}

inline void dfs1(int v,int p,int d,vi&dist) {
	dist[v]=d;
	for(auto to:tr[v])if(to!=p)dfs1(to,v,d+1,dist);
}

int dep[Nmax];
inline int dfs2(int v,int p) {
	int res=0;
	for(auto to:tr[v])if(to!=p)chmax(res,dfs2(to,v));
	return dep[v]=res+1;
}

inline void dfs3(int v,int p,const vi&dist) {
	vector<pi> ch;
	for(auto to:tr[v]) if(to!=p)ch.EB(dep[to],to);
	if(!ch.empty()) {
		swap(ch[0],*max_element(ALL(ch)));
		int len=ch.size()>1?max_element(ch.begin()+1,ch.end())->first:0;
		for(auto c:ch) {
			int to=c.second;
			while(!Empty() && dist[Last()]>=dist[v]-len) Pop();
			Push(v);
			dfs3(to,v,dist);
			if(!Empty() && Last()==v) Pop();
			chmax(len,c.first);
		}
		while(!Empty() && dist[Last()]>=dist[v]-len) Pop();
	}
	chmax(ans[v],curAns);
}

inline void Solve(int root,const vi&dist) {
	assert(Empty());
	dfs2(root,-1);
	dfs3(root,-1,dist);
}

signed main() {
	int n=read(),k=read();
	int root;
	REP(_,n-1) {
		int a=read()-1,b=read()-1;
		tr[a].PB(b);
		tr[b].PB(a);
	}
	REP(i,n) col[i]=read()-1;
	vi dist(n);
	dfs1(0,-1,0,dist);
	root= max_element(ALL(dist)) - dist.begin();
	dfs1(root,-1,0,dist);
	Solve(root,dist);
	root= max_element(ALL(dist)) - dist.begin();
	dfs1(root,-1,0,dist);
	Solve(root,dist);
	REP(i,n) print(ans[i]);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Joi 是一个用于 JavaScript 的强大的对象模型验证库。它可以用来验证和转换复杂的数据结构,如表单输入、API 请求和配置文件。Joi 提供了一组强大的验证规则和函数,可以轻松定义和应用对输入数据的验证逻辑。 Joi 的主要特点包括: 1. 可以通过链式调用来定义验证规则,使代码更加清晰和易读。 2. 支持各种类型的验证,包括字符串、数字、日期、枚举、数组、对象等。 3. 支持自定义验证规则和错误消息。 4. 提供丰富的验证函数,如必需字段、字符串长度、正则表达式匹配、数值范围、枚举值等。 5. 支持异步验证和自动转换。 6. 可以通过 `.validate()` 方法对数据进行验证,并返回验证结果。 以下是一个使用 Joi 进行表单验证的示例: ```javascript const Joi = require('joi'); // 定义验证规则 const schema = Joi.object({ username: Joi.string().alphanum().min(3).max(30).required(), password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')).required(), email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } }).required(), age: Joi.number().integer().min(18).max(99).required(), }); // 准备待验证的数据 const data = { username: 'john123', password: 'Password123', email: 'john@example.com', age: 25, }; // 进行验证 const result = schema.validate(data); // 输出验证结果 if (result.error) { console.log(result.error.details); } else { console.log('Validation passed'); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值