[ACNOI2022]感受孤独吧

122 篇文章 0 订阅
98 篇文章 0 订阅

题目

题目背景(这题心态给我搞崩了;写个背景放松一下)
回看我的博客,里面净是 D D ( X Y X ) \sf DD(XYX) DD(XYX) 。我便感到很悲哀了:分明说,历史是群众书写的,但是成为传奇的,只有英雄……

今天,当我再一次准备膜拜 D D ( X Y X ) \sf DD(XYX) DD(XYX) 时,他的脸色阴沉了下来。“弱者报团取暖,强者渴望孤独! O n e I n D a r k \sf OneInDark OneInDark,你天天抱团取暖,把我们的温度都给带坏了!” 只见他的双脚慢慢离开地面,他的身体飘向空中,“感受孤独吧!思考孤独吧!接受孤独吧!理解孤独吧!” 背着阳光,我眯眼看向他,一双 标杆眼 在空中泛起紫光——

“现在开始(口口有泥)!让世界感受孤独(谁该哟阔度国呦)!”

此时的空气静止如同凝固的水银。

srand(1) 反解(辛辣天塞)!”

题目描述
很不幸的是,srand(1) 反解是另一道题。😕

给出一棵树。你可以花费 1 1 1 的代价,标记一个点。当任何一个点 x x x 被标记后(即使不是你直接操作的点),到其距离不超过 r x r_x rx 的点也会被标记。求出标记所有点的最小代价。

数据范围与提示
n ⩽ 3 × 1 0 5 n\leqslant 3\times 10^5 n3×105,时限 3 s \rm 3s 3s

思路

a a a 被标记会导致 b b b 被标记,则连边 a → b a\to b ab,转化为图论问题,求入度为 0 0 0 s c c \rm scc scc 数量。

怎样优化建图?与树上路径有关,要想到 点分树 啊。当然本题不用建出显式点分树。

时间复杂度 O ( n log ⁡ 2 n ) \mathcal O(n\log^2 n) O(nlog2n)

代码

如果有虚点的入度为 0 0 0,会出问题。小心避免之。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <vector>
#include <cassert>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(const int &x){
	if(x > 9) writeint(x/10);
	putchar((x%10)^48);
}
inline void getMin(int &x, const int &y){
	if(y < x) x = y;
}
inline void getMax(int &x, const int &y){
	if(x < y) x = y;
}


const int MAXN = 6000005;
struct Edge{
	int to, nxt, val;
	Edge() = default;
	Edge(int _t, int _n, int _v):
		to(_t), nxt(_n), val(_v){ }
};
Edge e[MAXN<<1];
int head[MAXN], cntEdge;
void addEdge(int a, int b, int c = 0){
	e[cntEdge] = Edge(b,head[a],c), head[a] = cntEdge ++;
}
# define _go(i,x) for(int i=head[x]; ~i; i=e[i].nxt)

bool vis[MAXN]; int siz[MAXN], whole;
void scan(int &rt, int x, int pre = -1){
	siz[x] = 1; int mx = 0;
	_go(i,x) if((i^1) != pre && !vis[e[i].to]){
		scan(rt,e[i].to,i), siz[x] += siz[e[i].to];
		getMax(mx,siz[e[i].to]);
	}
	if(mx<<1 <= whole && whole <= siz[x]<<1) rt = x;
}

const int INF = 0x3fffffff;
int dep[MAXN], sta[MAXN], top;
void collect(int x, int pre = -1){
	if(dep[x] >= INF) return ; else sta[++ top] = x;
	_go(i,x) if((i^1) != pre && !vis[e[i].to])
		dep[e[i].to] = dep[x]+e[i].val, collect(e[i].to,i);
}

int r[MAXN], tmp[MAXN], cntNode; vector<int> G[MAXN];
bool cmp1(const int &x, const int &y){ return r[x]-dep[x] < r[y]-dep[y]; }
bool cmp2(const int &x, const int &y){ return dep[x] < dep[y]; }
void solve(int x){
	scan(x,x); vis[x] = true;
	top = dep[x] = 0; collect(x); memcpy(tmp+1,sta+1,top<<2);
	sort(sta+1,sta+top+1,cmp1), sort(tmp+1,tmp+top+1,cmp2);
	int *i = sta+1, *end_i = sta+top+1;
	for(int j=0,j_=0,lst=0; i!=end_i; ++i){
		while(j != top && dep[tmp[j+1]] <= r[*i]-dep[*i])
			++ j, G[cntNode+1].push_back(tmp[j]);
		if(j != j_){ // having different effects
			++ cntNode; if(lst) G[cntNode].push_back(lst);
			lst = cntNode; G[*i].push_back(lst); j_ = j;
		}
		else if(lst) G[*i].push_back(lst);
	}
	const int now_whole = whole;
	_go(j,x) if(!vis[e[j].to]){
		whole = siz[e[j].to] > siz[x] ? now_whole-siz[x] : siz[e[j].to];
		solve(e[j].to); // just recurse
	}
}

int bel[MAXN], dfn[MAXN], low[MAXN], tim;
bool inSta[MAXN]; extern int sta[MAXN], top;
void tarjan(int x){
	dfn[x] = low[x] = ++ tim;
	inSta[x] = true, sta[++ top] = x;
	for(const int &y : G[x])
		if(!dfn[y]) tarjan(y), getMin(low[x],low[y]);
		else if(inSta[y]) getMin(low[x],dfn[y]);
	if(low[x] == dfn[x]) for(; sta[top+1]!=x; --top)
		bel[sta[top]] = x, inSta[sta[top]] = false;
}

bool dead[MAXN];
int main(){
	int n = readint();
	memset(head+1,-1,n<<2);
	rep(i,1,n) r[i] = readint();
	for(int i=1,a,b,c; i!=n; ++i){
		a = readint(), b = readint(), c = readint();
		addEdge(a,b,c), addEdge(b,a,c);
	}
	whole = cntNode = n; solve(1); top = 0;
	memset(sta+1,0,(n+1)<<2); // needed!
	rep(i,1,cntNode) if(!dfn[i]) tarjan(i);
	rep(i,1,cntNode) for(const int &j : G[i])
		if(bel[i] != bel[j]) dead[bel[j]] = true;
	int ans = 0;
	rep(i,1,cntNode) if(!dead[bel[i]])
		++ ans, dead[bel[i]] = true;
	printf("%d\n",ans);
	return 0;
}

后记

就这么个玩意儿,我因为最初想错了(步了 H a n d I n D e v i l \sf HandInDevil HandInDevil 的后尘 😢)打了份错误代码调试了一下午;发现的时候,心态直接爆炸。去操场走了走,发现有个认识的在打球;随便投了两颗球,心情稍微好一点,但是困意依旧。

改成正确做法,代码又小错误不断;包括试图将新图存在旧图的 edge 里面,然后写了一堆 💩 出来……感觉整个人都浑浑噩噩……

希望天堂没有 d e b u g \rm debug debug

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值