[ACNOI2022]不是构造,胜似构造

122 篇文章 0 订阅

题目

题目描述
n n n m m m 边无向图。最初所有边都被 DDG \textsf{DDG} DDG 看守而不能通过。但是,记 S i S_i Si 为可通过的边构成的连通块中包含 i i i 的那个,若 ∑ x ∈ S u a x + ∑ y ∈ S v a y ⩾ e a , b \sum_{x\in S_u}a_x+\sum_{y\in S_v}a_y\geqslant e_{a,b} xSuax+ySvayea,b v ∉ S u v\notin S_u v/Su,则 ⟨ u , v ⟩ \langle u,v\rangle u,v 上可以建造黑色大桥(就不会受到 DDG \textsf{DDG} DDG 的侵扰)而因此可通过了!当然 S S S 也会对应变化。

在所有方案中,求出修建的黑色大桥总数最多的前提下,字典序最小的修建序列。

数据范围与提示
n ⩽ 1 0 5 n\leqslant 10^5 n105,边权 e u , v ⩽ 1 0 6 e_{u,v}\leqslant 10^6 eu,v106,点权 a i ∈ [ 0 , 1 0 6 ] a_i\in[0,10^6] ai[0,106]

思路

省流:没有任何参考价值。当成结论背下来算了。

第一反应:需维护 O ( deg ⁡ ) \mathcal O(\deg) O(deg) 的信息。启发式合并?大小点分治?都没走通。

隐晦的条件:一条边的判断条件只与两个值的变化有关。于是有了个不可能被我这等凡人想到的做法:对于每条边,设其 sum ( S u ) + sum ( S v ) \text{sum}(S_u)+\text{sum}(S_v) sum(Su)+sum(Sv) 还需 δ \delta δ 的增幅。则 sum ( S u ) \text{sum}(S_u) sum(Su) sum ( S v ) \text{sum}(S_v) sum(Sv) 至少其一拥有 ⌈ δ 2 ⌉ \lceil{\delta\over 2}\rceil 2δ 的增幅

因此把 t a g \rm tag tag 打上,被触发时检查,合并连通块就启发式合并。而一条边只会被检查 log ⁡ e \log e loge 次,因此总复杂度 O [ m log ⁡ m ( log ⁡ m + log ⁡ e ) ] \mathcal O[m\log m(\log m+\log e)] O[mlogm(logm+loge)] O ( m log ⁡ m log ⁡ e ) \mathcal O(m\log m\log e) O(mlogmloge)

代码

#include <cstdio>
#include <algorithm> // Almighty XJX yyds!!
#include <cstring> // oracle: ZXY yydBUS!!!
#include <cctype> // Huge Egg Dog ate me!!!
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <functional>
#include <utility>
#include <queue>
using namespace __gnu_pbds;
using llong = long long;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
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*10+(c^48);
	return a*f;
}

const int MAXN = 100000;
using PII = std::pair<int,int>;
tree<PII,null_type,std::less<PII>,splay_tree_tag> tre[MAXN];
int val[MAXN], fa[MAXN];
inline int findSet(int a){
	if(fa[a] == a) return a;
	return fa[a] = findSet(fa[a]);
}

std::priority_queue<int> todo;
int cost[MAXN<<1], ep[MAXN<<1][2];
std::queue<int> ans;
int main(){
	int n = readint(), m = readint();
	rep0(i,0,n) val[i] = readint(), fa[i] = i;
	for(int i=0,a,b; i!=m; ++i){
		a = ep[i][0] = readint()-1;
		b = ep[i][1] = readint()-1;
		cost[i] = readint();
		if(val[a]+val[b] >= cost[i]) todo.push(-i);
		else{ // half to check
			tre[a].insert(PII{(cost[i]+1+val[a]-val[b])>>1, i});
			tre[b].insert(PII{(cost[i]+1-val[a]+val[b])>>1, i});
		}
	}
	while(!todo.empty()){
		int x = -todo.top(); todo.pop();
		if(findSet(ep[x][0]) == findSet(ep[x][1])) continue;
		int faa = findSet(ep[x][0]), fab = findSet(ep[x][1]);
		if(tre[fab].size() > tre[faa].size()) tre[faa].swap(tre[fab]);
		for(const PII &v : tre[fab]) tre[faa].insert(v);
		val[faa] += val[fab], ans.push(x), fa[fab] = faa;
		while(!tre[faa].empty() && tre[faa].begin()->first <= val[faa]){
			x = tre[faa].begin()->second;
			tre[faa].erase(tre[faa].begin());
			if(findSet(ep[x][0]) == findSet(ep[x][1])) continue;
			int l = findSet(ep[x][0]), r = findSet(ep[x][1]);
			if(val[l]+val[r] >= cost[x]) todo.push(-x); // available
			else{ // still cut in half
				tre[l].insert(PII{(cost[x]+1+val[l]-val[r])>>1, x});
				tre[r].insert(PII{(cost[x]+1-val[l]+val[r])>>1, x});
			}
		}
	}
	printf("%d\n",int(ans.size()));
	for(; !ans.empty(); ans.pop())
		printf("%d ",ans.front()+1);
	putchar('\n');
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值