2020 Multi-University Training Contest 7 F题
有一个N个点的树,给予其中M个操作,每次选其中一个点x,{x, r, v},给它一个影响范围为r的权值为v的值,我们现在想要选取最多的权值点,使得两两之间是没有可重叠区间的。
这个问题画在一维平面上其实很好做,也就是对于一段排序,然后维护的就是一个线段树优化dp,当我们选取这个点pos的时候,我们只能选择的点,或者说,我们假设在
中,如果选择其他的更优,那么,我们可以不将这个点加进我们所选的集合中去。
于是,可以讲这个操作转换为,我们先将影响深度深的点给放进去,然后按照上面的类似贪心方法进行判断。
如果我们取这个点,比之前选取的点更优的话,我们可以选择这个点代替和它冲突的点,当然,还是可以继承和它不冲突的点的,于是,我们首先对影响区间最深的点开始这样的操作,这样的目的是为了我们其实只用考虑一个端点,而且用一个类似于差分的思想,我们可以判断取这个点是否更优。
画图说明:
如果存在这样一个点u,我们设它的深度为,然后对于u点假如存在一个影响的范围
,可以知道它最向上影响的深度为
的点,我们根据这个信息,来对所有的点进行
降序排序,于是,可以考虑之后放入的点v,如果被之前覆盖的点能够影响到的话,也就是对应它的向上
步的点(深度为
,或者当
为小于等于0时就是原树根节点),在点v的范围内,所以,我们将u的贡献点放在它向上r步的点,然后把它产生的贡献放在该点上。
然后,现在就是查询有多少的冲突贡献,我们可以算它覆盖范围内的点的权值和即可,当然,这里的“点”指的是原点向上r步的点。
贡献:假设冲突的点产生的总贡献为sum,那么这个点如果要取的话(如果它本身的价值还不足sum的话,要它何用?),那么它产生的贡献为。所以,对于放进去的第一个点,它的贡献就是
,因为在它之前就没有任何贡献了,所以根据这样的信息,我们可以维护出来所有点的关系,又因为它是根据“向上r步”的点来进行操作的,所以保证了影响点的深度一定是上升的,所以不存在先取之前的,而跳过中间的点的这样的情况。
操作实现
现在问题转化了,变成了求它的r步范围内的“权值点”的权值之和,那样,我们可以用动态点分治来实现,因为按照类似时间戳这样的方式进行插入,所以不可永久化,我们用一个数据结构维护这个关系即可(自然选用简单的树状数组啦!)。
所以,就是一个差分的关系了,我们找到这个点,然后不断的向它的点分治父亲节点走,每次算向周围距离的点的权值和,当然,这样就很容易把自己方向的点给多算了,所以点分治的时候记录在自己方向上的father的下一个点即可。
Code
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <bitset>
#include <unordered_map>
#include <unordered_set>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define HalF (l + r)>>1
#define lsn rt<<1
#define rsn rt<<1|1
#define Lson lsn, l, mid
#define Rson rsn, mid+1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define myself rt, l, r
using namespace std;
typedef unsigned long long ull;
typedef unsigned int uit;
typedef long long ll;
const int maxN = 1e5 + 7;
int N, Q;
namespace Graph
{
int head[maxN], cnt;
struct Eddge
{
int nex, to;
Eddge(int a=-1, int b=0):nex(a), to(b) {}
} edge[maxN << 1];
inline void addEddge(int u, int v)
{
edge[cnt] = Eddge(head[u], v);
head[u] = cnt++;
}
inline void _add(int u, int v) { addEddge(u, v); addEddge(v, u); }
inline void init()
{
cnt = 0;
for(int i=1; i<=N; i++) head[i] = -1;
}
};
using namespace Graph;
namespace Segement
{
vector<ll> tr[20][maxN];
void Clear()
{
for(int i=0; i<20; i++)
{
for(int j=1; j<=N; j++)
{
tr[i][j].clear();
}
}
}
inline ll query(vector<ll> &vt, int x) //x == distance + 1 in order to solve f(x == 0)
{
ll ans = 0;
while(x)
{
ans += vt[x];
x -= lowbit(x);
}
return ans;
}
inline void update(vector<ll> &vt, int x, ll val)
{
int _UP = (int)vt.size() - 1;
while(x <= _UP)
{
vt[x] += val;
x += lowbit(x);
}
}
};
using namespace Segement;
namespace Divide
{
int vis[maxN];
int maxx, root, all, fa[maxN], siz[maxN], son[maxN], dis_from_ff[maxN][20], under_ff[maxN][20];
void Init()
{
for(int i=1; i<=N; i++) vis[i] = 0;
maxx = INF;
}
void find_root(int u, int fa)
{
siz[u] = 1; son[u] = 0;
for(int i=head[u], v; ~i; i=edge[i].nex)
{
v = edge[i].to;
if(vis[v] || v == fa) continue;
find_root(v, u);
siz[u] += siz[v];
son[u] = max(son[u], siz[v]);
}
son[u] = max(son[u], all - siz[u]);
if(son[u] < maxx)
{
maxx = son[u];
root = u;
}
}
void dfs(int u, int father, int depth)
{
siz[u] = 1;
for(int i=head[u], v; ~i; i=edge[i].nex)
{
v = edge[i].to;
if(vis[v] || v == father) continue;
dis_from_ff[v][depth] = dis_from_ff[u][depth] + 1;
under_ff[v][depth] = under_ff[u][depth];
dfs(v, u, depth);
siz[u] += siz[v];
}
}
void node_Divide(int u, int depth)
{
vis[u] = depth;
tr[depth][u].resize(all + 2);
int tot_siz = all;
dis_from_ff[u][depth] = 0;
for(int i=head[u], v, tmp_siz; ~i; i=edge[i].nex)
{
v = edge[i].to;
if(vis[v]) continue;
dis_from_ff[v][depth] = dis_from_ff[u][depth] + 1;
under_ff[v][depth] = v;
tmp_siz = siz[u] > siz[v] ? siz[v] : all - siz[u];
tr[depth][v].resize(tmp_siz + 2);
dfs(v, u, depth);
}
for(int i=head[u], v; ~i; i=edge[i].nex)
{
v = edge[i].to;
if(vis[v]) continue;
all = siz[u] > siz[v] ? siz[v] : tot_siz - siz[u];
maxx = INF;
find_root(v, u);
fa[root] = u;
dis_from_ff[root][depth + 1] = 0;
under_ff[root][depth + 1] = root;
node_Divide(root, depth + 1);
}
}
};
using namespace Divide;
int deep[maxN], lca_fa[maxN][20];
void dfs_Deep(int u, int father)
{
deep[u] = deep[father] + 1; lca_fa[u][0] = father;
for(int i=head[u], v; ~i; i=edge[i].nex)
{
v = edge[i].to;
if(v == father) continue;
dfs_Deep(v, u);
}
}
struct Question
{
int x, r, v;
Question(int a=0, int b=0, int c=0):x(a), r(b), v(c) {}
inline void In() { scanf("%d%d%d", &x, &r, &v); }
friend bool operator < (Question e1, Question e2) { return deep[e1.x] - e1.r > deep[e2.x] - e2.r; }
} ques[maxN];
inline ll ask(int x, int r)
{
ll sum = 0;
int depth = vis[x];
sum = query(tr[depth][x], min(r + 1, (int)tr[depth][x].size() - 1));
int y = fa[x];
while(y)
{
depth--;
if(dis_from_ff[x][depth] <= r)
{
sum += query(tr[depth][y], min(r + 1 - dis_from_ff[x][depth], (int)tr[depth][y].size() - 1));
sum -= query(tr[depth][under_ff[x][depth]], min(r + 1 - dis_from_ff[x][depth], (int)tr[depth][under_ff[x][depth]].size() - 1));
}
y = fa[y];
}
return sum;
}
inline void push_into(int x, ll val)
{
int depth = vis[x];
update(tr[depth][x], 1, val);
int y = fa[x];
while(y)
{
depth--;
update(tr[depth][y], dis_from_ff[x][depth] + 1, val);
update(tr[depth][under_ff[x][depth]], dis_from_ff[x][depth] + 1, val);
y = fa[y];
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &N, &Q);
Graph::init();
Divide::Init();
Segement::Clear();
for(int i=1, u, v; i<N; i++)
{
scanf("%d%d", &u, &v);
_add(u, v);
}
for(int i=1; i<=Q; i++) ques[i].In();
all = N;
find_root(1, 0);
fa[root] = 0;
dis_from_ff[root][1] = 0;
under_ff[root][1] = root;
node_Divide(root, 1);
dfs_Deep(1, 0);
for(int j=1; (1 << j) < N; j++)
{
for(int i=1; i<=N; i++) lca_fa[i][j] = lca_fa[lca_fa[i][j - 1]][j - 1];
}
sort(ques + 1, ques + Q + 1);
Question now;
ll sum, ans = 0;
for(int i=1, x; i<=Q; i++)
{
now = ques[i];
sum = ask(now.x, now.r);
if(sum >= now.v) continue;
ans += now.v - sum;
x = now.x;
for(int j=19; j>=0; j--) if((now.r >> j) & 1) x = lca_fa[x][j];
if(!x) x = 1;
push_into(x, now.v - sum);
}
printf("%lld\n", ans);
}
return 0;
}