• Gym 100299E BZOJ 4054 [CERC2013]Escape (启发式合并)


    题目链接

    (Gym) https://codeforces.com/gym/100299
    (BZOJ) 大人,时代变了。

    题解

    一个显然的思路是树形 DP,设 (f[u]) 为一个二元组的集合,每个二元组 ((x,y)) 表示“如果我有至少 (x) 的血量,那么我可以多得到 (y) 的血量”。显然看成区间 ([x,x+y]) 后不交。
    一个思路是使用 set 维护这些二元组,每次启发式合并,把小的集合里的每个元素暴力插入大的集合;对于权值是负的节点 (u) 使用整体打标记的方法给所有的 (x) 都加上 (-w_u). 一个元素的插入可能导致集合中多个元素的合并,但因为一共只有 (O(nlog n)) 个元素,每次合并都会少一个元素,通过势能分析可知总共只会合并 (O(nlog n)) 次,复杂度是 (O(nlog^2 n))
    但是这样代码难度想想就恐怖……上 dark 看到了一份不到 1kb 的神仙代码,发现他用的是堆。
    于是脑补了如下做法:我们发现其实没有必要实时保证所有的 ([x,y]) 不交,只需要在每次把子树集合合并完后加入 (u) 号节点的时候,如果 (w_u) 是负的就从小到大进行一次合并,直到合并成正的为止。(详见代码)
    时间复杂度 (O(nlog^2n)),空间复杂度 (O(nlog n)).
    (突然意识到为什么我写了一个堆的启发式合并而没有想到可并堆这个东西/fad)

    代码

    #include<bits/stdc++.h>
    #define llong long long
    #define mkpr make_pair
    #define x first
    #define y second
    #define iter iterator
    #define riter reverse_iterator
    #define y1 Lorem_ipsum_
    #define tm dolor_sit_amet_
    #define pll pair<llong,llong>
    using namespace std;
    
    inline int read()
    {
    	int x = 0,f = 1; char ch = getchar();
    	for(;!isdigit(ch);ch=getchar()) {if(ch=='-') f = -1;}
    	for(; isdigit(ch);ch=getchar()) {x = x*10+ch-48;}
    	return x*f;
    }
    
    const int mxN = 2e5;
    const llong INF = 1e12;
    int n,t,en;
    struct Edge {int v,nxt;} e[mxN*2+3]; int fe[mxN+3]; int fa[mxN+3];
    llong a[mxN+3];
    priority_queue<pll,vector<pll>,greater<pll> > q[mxN+3];
    
    void addedge(int u,int v)
    {
    	en++; e[en].v = v;
    	e[en].nxt = fe[u]; fe[u] = en;
    }
    
    void dfs(int u)
    {
    	while(!q[u].empty()) {q[u].pop();}
    	for(int i=fe[u]; i; i=e[i].nxt)
    	{
    		int v = e[i].v; if(v==fa[u]) continue; fa[v] = u;
    		dfs(v);
    		if(q[v].size()>q[u].size()) {swap(q[u],q[v]);}
    		while(q[v].size()) {q[u].push(q[v].top()); q[v].pop();}
    	}
    	pll x; if(a[u]>=0ll) {x = mkpr(0ll,a[u]);} else {x = mkpr(-a[u],a[u]);}
    	while(!q[u].empty()&&(x.y<0ll||q[u].top().x<=x.x+x.y)) {pll y = q[u].top(); q[u].pop(); x = mkpr(max(x.x,y.x-x.y),x.y+y.y);}
    	if(x.y>=0ll) {q[u].push(x);}
    }
    
    int main()
    {
    	int T = read(); while(T--)
    	{
    		n = read(),t = read();
    		for(int i=1; i<=n; i++) a[i] = read();
    		for(int i=1; i<n; i++) {int u = read(),v = read(); addedge(u,v); addedge(v,u);}
    		n++; addedge(t,n); a[n] = INF;
    		dfs(1);
    		if(!q[1].empty()&&q[1].top().x==0ll&&q[1].top().y>=INF/2ll) {puts("escaped");} else {puts("trapped");}
    		for(int i=1; i<=n; i++) fe[i] = fa[i] = 0;
    		for(int i=1; i<=en; i++) e[i].v = e[i].nxt = 0;
    		en = 0;
    	}
    	return 0;
    }
    
  • 相关阅读:
    05.scrapy框架的UA池和代理池
    04scrapy框架的日志等级和请求传参
    03.scrapy框架之递归解析和post请求
    02.scrapy框架持久化存储
    git
    01.scrapy框架简介和基础应用
    将配置文件由Dos存为unix格式
    利用alias命令别名对常用运维命令的调用 (给命令起别名)
    使用LVM方式扩容硬盘空间
    Vi编辑器添加删除多行注释
  • 原文地址:https://www.cnblogs.com/suncongbo/p/14270921.html
Copyright © 2020-2023  润新知