目录
@description@
在比特大陆上有 n 个城市,它们按照海拔从高到低依次被标记为 1,2,…,n,任意两个城市的海拔都不相同。有一条河流发源于海拔最高的 1 号城市,经过 n−1 次分流,流经了所有 n 个城市,形成了一棵以 1 为根的有根树结构。
每个城市都开着一家零件销售店,在接下来的 k 天内,比特大陆上一共存在过 m 家零件生产厂,第 i 家零件生产厂于第 li 天在第 xi 个城市开张,于第 ri 天关闭,其中第 li 天和第 ri 天这家工厂也是正常运作的,一共运作了 ri−li+1 天。所有工厂从开张到关闭都没有迁移过厂址。
你是一名零件贸易市场的中介。你的工作是在接下来的 k 天内,每天为每家正常运作的工厂选择至多一家零件销售店去供货。一家工厂每天只能选择一家零件销售店去供货,或者什么也不做;一家零件销售店每天也只能接受一家工厂的供货,或者什么也不做;每家工厂可以在不同的日子里更换供给的零件销售店。需要注意的是,这些工厂只会选择沿着河流方向,从 xi 号城市开始往子树内送货(包含 xi 号城市)。
对于第 i 家工厂,如果某一天它被选择向某家零件销售店供货,那么你所在的零件贸易市场在当天将会获得 vi 元的提成。请写一个程序,在接下来的 k 天内,每天为每家工厂选择至多一家零件销售店,使得当天的总提成最大。
input
第一行包含三个正整数 n,k,m,分别表示城市的数量、天数以及工厂的数量。
第二行包含 n−1 个正整数 f2,f3,…,fn,依次表示每个城市的父亲城市。
接下来 m 行,每行四个正整数 xi,vi,li,ri,依次描述每个工厂。
output
输出 k 行,每行输出一个整数,第 i 行输出第 i 天的总提成的最大可能值。
sample input
5 6 5
1 2 3 1
5 1 2 5
5 2 3 4
3 1 1 1
3 2 1 1
3 3 1 1
sample output
5
1
2
2
1
0
对于 100% 的数据,1≤fi<i,1≤xi≤n≤100000,1≤vi≤10^9,1≤li≤ri≤k≤100000,m≤100000。
@solution@
@part - 1@
先考虑只有一天的情况。
显然可以当做一个最大权匹配来做,工厂连向所有它可以到达的销售店。
当我们用最小费用流解决最大权匹配的时候,总是会选取最长路増广。
类比这个思路,我们每次选择当前权值最大的工厂,尝试将它匹配。匹配失败直接抛弃该工厂。
实际上询问是否可以将新加入的工厂进行匹配,相当于询问当前这个图是否存在完美匹配。
我们每次都要为每一个工厂找一个它匹配的点吗?这样的话无论如何其实跟直接跑费用流没什么差别。
令 siz[i] 表示以点 i 为根的子树的大小,注意到以点 i 为根的子树中最多匹配 siz[i] 的工厂。
可以证明:如果对于每一个点 i,以该点为根的子树中工厂数量 <= siz[i],总是存在完美匹配。
使用归纳法即可。据说这个是 hall 定理的推广,可我并不知道 hall 定理是什么。
我们对每一个点记录值 re[i] 表示以 i 为根的子树中还可以塞多少工厂进去。
从大到小加入工厂,将该工厂所在点到根上路径所有点的 re 减去 1,查询工厂所在点到树根上 re 的最小值是否为 -1。
如果为 -1 就将减去的 1 加回来,并删除该工厂的贡献。
使用树剖 O(mlog^2 n)。
@part - 2@
考虑随着时间的推移,有工厂加入应该怎么办。
先同上面一样,先将该工厂所在点到根上路径所有点的 re 减去 1,查询工厂所在点到树根上 re 的最小值是否为 -1。
但此时,如果为 -1,就不能简单地不考虑该工厂的贡献。因为不是从大到小加入而是随时间加入,可能删去以前的某贡献较小的工厂给当前工厂让位置更优。
我们可以找到离工厂最近的 re = -1 的点 x,通过删去以 x 为根的子树中贡献最小的工厂使 re 恢复正常范围。
可以简单地证明该方法的正确性,因为这样操作后,所有点的 re 都是合法,且不能找到贡献更小的工厂使 re 合法。
找到离工厂最近的 re = -1 的点还是可以用树剖,找到某子树内的贡献最小的工厂可以求一个 dfs 序再用线段树。
注意这里的 dfs 序存储的是工厂而不是树上的点。之所以要强调这个是因为树上的一个点可以对应多个工厂。
线段树的复杂度是 O(mlog m) 的,在树剖的 O(mlog^2 m) 面前不值一提。
@part - 3@
考虑有工厂的删除怎么办。因为工厂的删除可能会导致以前某些工厂的再加入,所以并不是一件容易的事情。
但是注意到我们可以离线。于是我们可以用分治来回避删除的问题(参考bzoj 4025的离线分治做法)
我们对时间进行分治。当分治到区间 [l, r] 时,将存在时间包含 [l, r] 的工厂加入当前的树上。
然后递归 [l, mid] 与 [mid+1, r]。递归到底层 [i, i] 即可求出第 i 时刻的答案。
从 [l, r] 递归出来时,将所做的操作撤回(注意撤回不等于删除,所以是可做的)
看起来非常的完美。
计算时间复杂度,分治有个 log,树剖有两个 log,一共就要三个 log。
哦豁。完蛋。
@part - 4@
有一个替代树剖的黑科技:全局平衡二叉树。
比起树剖的两个 log,它的理论复杂度只有一个 log。
也不同于 lct 的大常数,它的常数很小,不涉及到均摊分析。
它相对于树剖,更加考虑整棵树的平衡性,类似于 lct。
它相对于 lct,除去了树加边减边的操作,充分利用了树的形态是静止的这一性质,类似于树剖。
说白了就是 lct 与树剖两者混合。
然而树剖常数小,而且如果不是恶意卡树剖事实上卡不满两个 log。
所以一般没怎么用。
实际上,全局平衡二叉树是一颗二叉树森林,其中的每颗二叉树维护一条重链。但是这个森林里的二叉树又互有联系,其中每个二叉树的根连向这个重链链头的父亲,就像LCT中一样。
我们的目的是使这个大二叉树森林树高log。
于是我们对一条重链构建二叉树的时候,实际上可以理解成每个点点权是带权的,点权为轻子树的点数和+1,然后每次我们取这个链的带权中点(转载注:这里的带权中点实际上就是带权重心)作为根,递归处理子树。
——转自该博客
这篇博客的语言足够精炼了,但我还是想做一点小小的补充:
每棵二叉树实际上是一棵以深度为关键字的平衡树(类似于lct),可以像 splay 维护区间信息一样进行维护。
时间复杂度的证明,考虑从某个点开始往父亲爬。令一个点所代表的连通块的大小为它在全局平衡二叉树内的子树大小(包括轻儿子)。
则经过轻边时,由树链剖分的复杂度证明可得,连通块的大小至少扩大两倍。
经过重边时,由带权重心的定义简单推导可得,连通块的大小至少扩大两倍。
于是 log n 可证。
然后时间复杂度就是 O(nlog^2 n)。
@accepted code@
//全局平衡二叉树中使用了标记永久化
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 100000 + 5;
const int INF = 1<<30;
int n, k, m;
struct tree{
struct node{
node *ch[2], *fa;
int tag, mn;
}pl[MAXN], *nd[MAXN], *NIL;
void debug() {
for(int i=0;i<=n;i++)
printf("! %d : %d %d %d | %d %d\n", i, pl[i].fa-pl, pl[i].ch[0]-pl, pl[i].ch[1]-pl, pl[i].tag, pl[i].mn);
puts("");
}
void init() {
nd[0] = NIL = &pl[0];
NIL->ch[0] = NIL->ch[1] = NIL->fa = NIL, NIL->tag = NIL->mn = INF;
for(int i=1;i<=n;i++) {
nd[i] = &pl[i];
nd[i]->ch[0] = nd[i]->ch[1] = nd[i]->fa = NIL;
nd[i]->tag = nd[i]->mn = 0;
}
}
void set_child(node *x, node *y, int d) {
if( x != NIL ) x->ch[d] = y;
if( y != NIL ) y->fa = x;
}
bool is_root(node *x) {
return x->fa->ch[0] != x && x->fa->ch[1] != x;
}
int pushup(node *x) {
x->mn = min(0, min(x->ch[0]->mn, x->ch[1]->mn)) + x->tag;
}
void modify(node *x, int del) {
while( x != NIL ) {
x->tag += del;
if( x->ch[1] != NIL ) {
x->ch[1]->tag -= del;
pushup(x->ch[1]);
}
while( !is_root(x) ) {
if( x->fa->ch[1] == x )
x->fa->tag += del, x->tag -= del;
pushup(x), x = x->fa;
}
pushup(x), x = x->fa;
}
}
int fun(node *x) {
int ret = x->tag;
while( !is_root(x) )
x = x->fa, ret += x->tag;
return ret;
}
int query(node *x) {
while( x != NIL ) {
int k = fun(x);
if( k == -1 ) return x - pl;
else if( x->ch[0]->mn + k == -1 ) {
x = x->ch[0]; k += x->tag;
while( true ) {
if( x->ch[1]->mn + k == -1 ) x = x->ch[1];
else if( k == -1 ) return x - pl;
else x = x->ch[0];
k += x->tag;
}
}
while( !is_root(x) ) {
k -= x->tag;
if( x->fa->ch[1] == x ) {
if( k == -1 ) return x->fa - pl;
else if( x->fa->ch[0]->mn + k == -1 ) {
x = x->fa->ch[0]; k += x->tag;
while( true ) {
if( x->ch[1]->mn + k == -1 ) x = x->ch[1];
else if( k == -1 ) return x - pl;
else x = x->ch[0];
k += x->tag;
}
}
}
x = x->fa;
}
x = x->fa;
}
return NIL - pl;
}
}T1;
ll res, ans[MAXN];
vector<int>G[MAXN];
int siz[MAXN], hvy[MAXN], fa[MAXN];
void dfs1(int x) {
siz[x] = 1;
for(int i=0;i<G[x].size();i++) {
int p = G[x][i];
dfs1(p);
siz[x] += siz[p];
if( siz[p] > siz[hvy[x]] )
hvy[x] = p;
}
}
int lsiz[MAXN];
int arr[MAXN];
void T1_build(int l, int r, int fa, int dir) {
if( l > r ) return ;
ll sum = 0, tmp1 = 0;
for(int i=l;i<=r;i++) sum += lsiz[arr[i]];
int x = l; ll k = sum - lsiz[arr[l]];
for(int i=l;i<=r;i++) {
ll tmp2 = sum - tmp1 - lsiz[arr[i]];
if( max(tmp1, tmp2) < k )
k = max(tmp1, tmp2), x = i;
tmp1 += lsiz[arr[i]];
}
if( dir == -1 ) T1.nd[arr[x]]->fa = T1.nd[fa];
else T1.set_child(T1.nd[fa], T1.nd[arr[x]], dir);
T1_build(l, x - 1, arr[x], 0);
T1_build(x + 1, r, arr[x], 1);
}
void dfs2(int x, int tp) {
for(int i=0;i<G[x].size();i++)
if( G[x][i] != hvy[x] )
dfs2(G[x][i], G[x][i]);
lsiz[x] = siz[x] - siz[hvy[x]];
if( hvy[x] ) dfs2(hvy[x], tp);
if( x == tp ) {
int cnt = 0, fx = fa[x];
while( x ) {
arr[++cnt] = x;
x = hvy[x];
}
T1_build(1, cnt, fx, -1);
}
}
vector<int>fv[MAXN];
int fir[MAXN], bac[MAXN], num[MAXN], key[MAXN], pos[MAXN], dcnt = 0;
void dfs3(int x) {
fir[x] = dcnt + 1;
for(int i=0;i<fv[x].size();i++)
num[fv[x][i]] = (++dcnt);
for(int i=0;i<G[x].size();i++)
dfs3(G[x][i]);
bac[x] = dcnt;
}
struct segtree{
struct node{
node *ch[2];
int mn, l, r;
}pl[3*MAXN], *ncnt, *root;
int comp(int x, int y) {
if( x == 0 ) return y;
if( y == 0 ) return x;
if( key[x] < key[y] ) return x;
else return y;
}
void pushup(node *x) {
x->mn = comp(x->ch[0]->mn, x->ch[1]->mn);
}
node *build(int l, int r) {
node *x = (++ncnt); x->mn = 0, x->l = l, x->r = r;
if( l == r ) return x;
int mid = (l + r) >> 1;
x->ch[0] = build(l, mid);
x->ch[1] = build(mid + 1, r);
return x;
}
void init() {
ncnt = &pl[0], root = build(1, m);
}
void modify(node *x, int p, int k) {
if( p > x->r || p < x->l ) return ;
if( x->l == x->r ) {
x->mn = k;
return ;
}
modify(x->ch[0], p, k), modify(x->ch[1], p, k);
pushup(x);
}
int query(node *x, int l, int r) {
if( l > x->r || r < x->l ) return 0;
if( l <= x->l && x->r <= r )
return x->mn;
return comp(query(x->ch[0], l, r), query(x->ch[1], l, r));
}
void debug(node *x) {
printf("* %d %d %d\n", x->l, x->r, x->mn);
if( x->l == x->r ) return ;
debug(x->ch[0]), debug(x->ch[1]);
}
}T2;
void prepare() {
dfs1(1), T1.init(), dfs2(1, 1);
for(int i=1;i<=n;i++) T1.modify(T1.nd[i], 1);
dfs3(1), T2.init();
}
struct factory{
int l, r, id;
factory(int _l=0, int _r=0, int _id=0):l(_l), r(_r), id(_id){}
};
struct modify{
int x, y, z;
modify(int _x=0, int _y=0, int _z=0):x(_x), y(_y), z(_z){}
}stk[5*MAXN];
int tp;
void insert(int x) {
//printf("? %d\n", x);
res += key[x], T1.modify(T1.nd[pos[x]], -1), T2.modify(T2.root, num[x], x);
stk[++tp] = modify(1, pos[x], 1), stk[++tp] = modify(2, x, 0);
int p = T1.query(T1.nd[pos[x]]);
if( p ) {
int q = T2.query(T2.root, fir[p], bac[p]);
//printf(". %d %d %d\n", fir[p], bac[p], q);
res -= key[q], T1.modify(T1.nd[pos[q]], 1), T2.modify(T2.root, num[q], 0);
stk[++tp] = modify(1, pos[q], -1), stk[++tp] = modify(2, q, q);
}
//T2.debug(T2.root); T1.debug();
}
void restore(int x) {
while( tp != x ) {
if( stk[tp].x == 1 ) T1.modify(T1.nd[stk[tp].y], stk[tp].z);
else {
if( stk[tp].z == 0 ) res -= key[stk[tp].y];
else res += key[stk[tp].y];
T2.modify(T2.root, num[stk[tp].y], stk[tp].z);
}
tp--;
}
}
void solve(int L, int R, vector<factory>v) {
int mid = (L + R) >> 1, nw = tp;
vector<factory>vl, vr;
for(int i=0;i<v.size();i++) {
if( v[i].l <= L && R <= v[i].r ) insert(v[i].id);
else {
if( L != R ) {
if( v[i].l <= mid && v[i].r >= L ) vl.push_back(v[i]);
if( v[i].l <= R && v[i].r >= mid + 1 ) vr.push_back(v[i]);
}
}
}
if( L == R ) ans[L] = res;
else solve(L, mid, vl), solve(mid + 1, R, vr);
restore(nw);
}
int main() {
//freopen("data.in", "r", stdin);
scanf("%d%d%d", &n, &k, &m);
for(int i=2;i<=n;i++) {
scanf("%d", &fa[i]);
G[fa[i]].push_back(i);
}
vector<factory>vec;
for(int i=1;i<=m;i++) {
int x, v, l, r;
scanf("%d%d%d%d", &x, &v, &l, &r);
vec.push_back(factory(l, r, i));
fv[x].push_back(i); key[i] = v; pos[i] = x;
}
prepare();
solve(1, k, vec);
for(int i=1;i<=k;i++)
printf("%lld\n", ans[i]);
}
@details@
康复计划 - 7。
当我拿到这道题的时候,我的内心是拒绝的。
但是老师一定要让我写,我。。。
调到最后
总结一下错误:
(1)线段树、全局平衡二叉树、原树中的点的编号以及工厂序号等搞混,虽说这是挺常见的错误,不过还是在犯。。。
(2)用标记永久化,然后在找 -1 的时候没有累加标记,然后卡在死循环里了。
(3)因为平衡树左边权值右边权值小,习惯性地往左边跑。然而左边是深度较小的点,我们要找的是靠近下面(即深度较大)的点。
靠着常数小暂时拿了排行榜第二,标算太可怕了拉我几秒的总时间。