2021-09-07 test
[TJOI2013]拯救小矮人
考试题目的数据加强为2e5
,所以此题做法应为
O
(
n
log
n
)
O(n\log n)
O(nlogn)的反悔贪心
这种有多元属性,选择最优的问题
如果发现简单的贪心总是半错半对的
解法通常有两种
- 设计
dp
进行转移- 考虑
反悔贪心
此题也不意外
定义每个人所需的逃出高度为 H − a i − b i H-a_i-b_i H−ai−bi
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 H−a1−b1>H−a2−b2⇒a1+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 h−a1+a2+b2≥H
如果2先逃走,1也能逃走,则必须满足 h − a 2 + a 1 + b 1 ≥ H h-a_2+a_1+b_1\ge H h−a2+a1+b1≥H
-
− 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+b1⇔a2+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+b2⇒a1<a2+b2−b1
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+b2−b1+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 的火车
恰好考了一下最近学的基环树
如果是问从每个点开始能访问的点数,那么就非常简单,是个外环树
每个非环树上的点的路径都是一条链,只需要考虑深度和 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;
}