[TJOI2013]拯救小矮人(反悔贪心证明),「ICPC World Finals 2019」Hobson 的火车(基环树,差分)

这篇博客探讨了在竞赛编程中如何利用反悔贪心策略解决复杂问题,例如在「ICPC World Finals 2019」的Hobson的火车问题中,通过优先队列实现O(nlogn)的解决方案。同时,文章还介绍了树上差分的概念,并展示了如何应用于解决内环树问题,从而确定每个点可以被多少个点访问到。
摘要由CSDN通过智能技术生成

[TJOI2013]拯救小矮人

luogu4823

考试题目的数据加强为2e5,所以此题做法应为 O ( n log ⁡ n ) O(n\log n) O(nlogn)反悔贪心

这种有多元属性,选择最优的问题

如果发现简单的贪心总是半错半对的

解法通常有两种

  • 设计dp进行转移
  • 考虑反悔贪心

此题也不意外

定义每个人所需的逃出高度为 H − a i − b i H-a_i-b_i Haibi

observation1 : 逃出高度越高的人越先逃出

当然可能某些人的属性是极端的,比如身高非常高而手很短,却被派到前面,这个时候可能不让此人逃走能让后面的人走得更多,才是最佳方案

从这种情况上得出

observation2 : 如果断定此人不逃出,那么就永远不会再给机会逃出

observation3 : 如果此人不能逃出,但是前面逃出的人有高度更高的,就把最高的那个人扔回来,换成这个人逃出

到这里就可以使用优先队列维护做了

接下来给出性质一和性质三的证明

  • 性质一:

    考虑先逃走的人逃出高度为 h 1 h_1 h1,再逃走的逃出高度为 h 2 h_2 h2

    则有 h 1 > h 2 h_1>h_2 h1>h2,即 H − a 1 − b 1 > H − a 2 − b 2 ⇒ a 1 + b 1 < a 2 + b 2 H-a_1-b_1>H-a_2-b_2\Rightarrow a_1+b_1<a_2+b_2 Ha1b1>Ha2b2a1+b1<a2+b2

    h h h为还在洞内所有人的身高包含1,2

    如果1,2都能逃出,那么1,2之间逃出的顺序,对后面的人不会影响,反正都不会有身高的贡献

    也就是说,我们需要证明当满足 h 1 > h 2 h_1>h_2 h1>h2条件时,1更难逃出,所以需要先走

    既然2能逃走,说明不需要1的身高,即 h − a 1 + a 2 + b 2 ≥ H h-a_1+a_2+b_2\ge H ha1+a2+b2H

    如果2先逃走,1也能逃走,则必须满足 h − a 2 + a 1 + b 1 ≥ H h-a_2+a_1+b_1\ge H ha2+a1+b1H

    • − a 1 + a 2 + b 2 ? − a 2 + a 1 + b 1 ⇔ a 2 + a 2 + b 2 ? a 1 + a 1 + b 1 -a_1+a_2+b_2\quad?\quad -a_2+a_1+b_1\Leftrightarrow a_2+a_2+b_2\quad ?\quad a_1+a_1+b_1 a1+a2+b2?a2+a1+b1a2+a2+b2?a1+a1+b1

      a 1 + b 1 < a 2 + b 2 ⇒ a 1 < a 2 + b 2 − b 1 a_1+b_1<a_2+b_2\Rightarrow a_1<a_2+b_2-b_1 a1+b1<a2+b2a1<a2+b2b1

      a 1 + a 1 + b 1 < a 2 + b 2 − b 1 + a 1 + b 1 = a 2 + b 2 + a 1 a_1+a_1+b_1<a_2+b_2-b_1+a_1+b_1=a_2+b_2+a_1 a1+a1+b1<a2+b2b1+a1+b1=a2+b2+a1

    所以?应为>

    也就是说2逃出时超出 H H H的距离更多,也就是比1逃出条件要松一点

    如果1逃走后2无法出逃,那么2逃走后1更无法逃出,且加紧了后面的约束

    当然这只是充分证明,因为会考虑逃不走换人的情况,结合反悔贪心才是最后的贪心

  • 性质三:

    对于某个不能逃出的人,选择前面比自己高的最高的人,换进来

    这反悔贪心很显然,换一个人进来换一个人出去,对人数没有影响

    但是换了个更高的人进来,那么其高度就会松弛后面所有人的逃出条件,使得逃出可能性增大

    • 接下来证明,在逃出条件越后越松弛的前提下,换一个更高的人进来,自己一定可以逃出去

      假设2此时无法逃出,需要把前面的1换进来,定义 h h h为在洞内的人的高度加上已经逃出的1的高度

      a 1 + b 1 < a 2 + b 2 a_1+b_1<a_2+b_2 a1+b1<a2+b2 a 1 > a 2 a_1>a_2 a1>a2

      把在1,2之间逃出洞的所有人先再次丢回洞中,相当于回溯到第一次决定让1逃出的时刻

      因为逃出高度限制是逐渐宽松的,既然这个时候1能逃出,2一定也能逃出

      考虑变成2逃出对之前在1,2中间逃出的人的影响,发现是正面影响

      因为2的高度没有1高,相当于换了一个更高的人垫背,那么原来在1,2中间逃出的人同样也会逃离

#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long 
#define maxn 200005
priority_queue < int > q;
struct node { int a, b, h; }p[maxn];
int n, H;
int h[maxn];

signed main() {
	scanf( "%lld", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lld %lld", &p[i].a, &p[i].b );
	scanf( "%lld", &H );
	for( int i = n;i;i -- )
		p[i].h = H - p[i].a - p[i].b;
	sort( p + 1, p + n + 1, []( node x, node y ) { return x.h > y.h; } );
	for( int i = n;i;i -- )
		h[i] = h[i + 1] + p[i].a;
	int now = 0, ans = 0;
	for( int i = 1;i <= n;i ++ )	
		if( now + h[i + 1] >= p[i].h ) ans ++, q.push( p[i].a );
		else {
			if( ! q.empty() and q.top() > p[i].a )
				now += q.top(), q.pop(), q.push( p[i].a );
			else
				now += p[i].a;
		}
	printf( "%lld\n", ans );
	return 0;
}

「ICPC World Finals 2019」Hobson 的火车

LOJ6548

恰好考了一下最近学的基环树

如果是问从每个点开始能访问的点数,那么就非常简单,是个外环树

每个非环树上的点的路径都是一条链,只需要考虑深度和 k k k的关系即可

但不巧的是这道题是求每个点可以被多少个点访问到,是个内环树

发现时间卡在设计 D P DP DP k k k转移上

很妙的,又是前几天考试的解法就是——树上差分和环上差分

具体细节实现可看代码及注释

#include <cstdio>
#include <vector>
using namespace std;
#define maxn 500005
vector < int > G[maxn], circle;
int n, k, siz, top;
int ans[maxn], dep[maxn], vis[maxn], s[maxn], d[maxn];

void dfs( int u, int rt ) {
	if( ! vis[u] ) vis[u] = 1; //在求该联通分量时可能有些点还未访问过 身处环遍历后面 
	s[++ top] = u;
	if( top > k + 1 and s[top - k - 1] ^ circle[rt] )	//差分 与u点相距k+1甚至更远的点不再有贡献 
		ans[s[top - k - 1]] --;
	for( auto v : G[u] )
		if( vis[v] == 2 ) continue;//环点在后面单独更新
		else { 
			dep[v] = dep[u] + 1;
			dfs( v, rt );
			if( u ^ circle[rt] ) ans[u] += ans[v];
		}
	top --;
	if( dep[u] <= k ) { //会延伸到部分环点 哪怕只有一个点 
	//环长虽然是siz 但实际上只需要走siz-1步就会遍历完所有环点 
		if( dep[u] + siz > k + 1 ) { //无法在k步内将环都覆盖完 
			int t = ( rt + k - dep[u] + 1 ) % siz;
			/*
			rt+k-dep[u]是真正差分的结束位置
			需要在结束位置的下一位pos+1打上-1抵消标记 
			*/
			ans[circle[rt]] ++, ans[circle[t]] --;
			if( t < rt ) ans[circle[0]] ++;
			/*
			由于环差分也是从环头遍历到环尾 
			默认断开了环头与环尾的边 
			该if成立说明覆盖的部分点跨越了环尾在环头多延伸了一点 
			环头到结束位置也应该差分
			*/ 
		}
		else ++ ans[circle[0]]; //该点可以访问所有环点 直接在环头++ 
	}
	if( u ^ circle[rt] ) ans[u] ++; 
}

void dfs( int now ) {
	circle.clear();
	while( vis[now] ^ 2 ) { //如果now点先前已经被遍历2次 也就是回到了环头 不再重复入环 
		if( vis[now] ) circle.push_back( now ); 
		//再次经过now点说明是环上一点 开始进入求环部分 
		vis[now] ++;
		now = d[now];
	}
	siz = circle.size();
	for( int i = 0;i < siz;i ++ )//先差分求每个环点的非环树答案 
		dfs( circle[i], i );
	for( int i = 1;i < siz;i ++ )//环差分 
		ans[circle[i]] += ans[circle[i - 1]];
}

int main() {
	scanf( "%d %d", &n, &k );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%d", &d[i] );
		G[d[i]].push_back( i );//建反图 
	}
	for( int i = 1;i <= n;i ++ )
		if( ! vis[i] ) dfs( i );
	for( int i = 1;i <= n;i ++ )
		printf( "%d\n", ans[i] );
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值