2021-09-07 test
- [TJOI2013]拯救小矮人
- 「ICPC World Finals 2019」Hobson 的火车
[TJOI2013]拯救小矮人
luogu4823
考试题目的数据加强为2e5
,所以此题做法应为O(nlogn)O(n\log n)O(nlogn)的反悔贪心
这种有多元属性,选择最优的问题
如果发现简单的贪心总是半错半对的
解法通常有两种
- 设计
dp
进行转移- 考虑
反悔贪心
此题也不意外
定义每个人所需的逃出高度为H−ai−biH-a_i-b_iH−ai−bi
observation1
: 逃出高度越高的人越先逃出
当然可能某些人的属性是极端的,比如身高非常高而手很短,却被派到前面,这个时候可能不让此人逃走能让后面的人走得更多,才是最佳方案
从这种情况上得出
observation2
: 如果断定此人不逃出,那么就永远不会再给机会逃出
observation3
: 如果此人不能逃出,但是前面逃出的人有高度更高的,就把最高的那个人扔回来,换成这个人逃出
到这里就可以使用优先队列维护做了
接下来给出性质一和性质三的证明
-
性质一:
考虑先逃走的人逃出高度为h1h_1h1,再逃走的逃出高度为h2h_2h2
则有h1>h2h_1>h_2h1>h2,即H−a1−b1>H−a2−b2⇒a1+b1<a2+b2H-a_1-b_1>H-a_2-b_2\Rightarrow a_1+b_1<a_2+b_2H−a1−b1>H−a2−b2⇒a1+b1<a2+b2
设hhh为还在洞内所有人的身高包含1,2
如果1,2都能逃出,那么1,2之间逃出的顺序,对后面的人不会影响,反正都不会有身高的贡献
也就是说,我们需要证明当满足h1>h2h_1>h_2h1>h2条件时,1更难逃出,所以需要先走
既然2能逃走,说明不需要1的身高,即h−a1+a2+b2≥Hh-a_1+a_2+b_2\ge Hh−a1+a2+b2≥H
如果2先逃走,1也能逃走,则必须满足h−a2+a1+b1≥Hh-a_2+a_1+b_1\ge Hh−a2+a1+b1≥H
-
−a1+a2+b2?−a2+a1+b1⇔a2+a2+b2?a1+a1+b1-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+b1⇔a2+a2+b2?a1+a1+b1
a1+b1<a2+b2⇒a1<a2+b2−b1a_1+b_1<a_2+b_2\Rightarrow a_1<a_2+b_2-b_1a1+b1<a2+b2⇒a1<a2+b2−b1
a1+a1+b1<a2+b2−b1+a1+b1=a2+b2+a1a_1+a_1+b_1<a_2+b_2-b_1+a_1+b_1=a_2+b_2+a_1a1+a1+b1<a2+b2−b1+a1+b1=a2+b2+a1
所以
?
应为>
也就是说2逃出时超出HHH的距离更多,也就是比1逃出条件要松一点
如果1逃走后2无法出逃,那么2逃走后1更无法逃出,且加紧了后面的约束
当然这只是充分证明,因为会考虑逃不走换人的情况,结合反悔贪心才是最后的贪心
-
-
性质三:
对于某个不能逃出的人,选择前面比自己高的最高的人,换进来
这反悔贪心很显然,换一个人进来换一个人出去,对人数没有影响
但是换了个更高的人进来,那么其高度就会松弛后面所有人的逃出条件,使得逃出可能性增大
-
接下来证明,在逃出条件越后越松弛的前提下,换一个更高的人进来,自己一定可以逃出去
假设2此时无法逃出,需要把前面的1换进来,定义hhh为在洞内的人的高度加上已经逃出的1的高度
则a1+b1<a2+b2a_1+b_1<a_2+b_2a1+b1<a2+b2 且 a1>a2a_1>a_2a1>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 );elsenow += p[i].a;}printf( "%lld\n", ans );return 0;
}
「ICPC World Finals 2019」Hobson 的火车
LOJ6548
恰好考了一下最近学的基环树
如果是问从每个点开始能访问的点数,那么就非常简单,是个外环树
每个非环树上的点的路径都是一条链,只需要考虑深度和kkk的关系即可
但不巧的是这道题是求每个点可以被多少个点访问到,是个内环树
发现时间卡在设计DPDPDP的kkk转移上
很妙的,又是前几天考试的解法就是——树上差分和环上差分
具体细节实现可看代码及注释
#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;
}