@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)因为平衡树左边权值右边权值小,习惯性地往左边跑。然而左边是深度较小的点,我们要找的是靠近下面(即深度较大)的点。

靠着常数小暂时拿了排行榜第二,标算太可怕了拉我几秒的总时间。

@noi.ac - 493@ trade的更多相关文章

  1. # NOI.AC省选赛 第五场T1 子集,与&最大值

    NOI.AC省选赛 第五场T1 A. Mas的童年 题目链接 http://noi.ac/problem/309 思路 0x00 \(n^2\)的暴力挺简单的. ans=max(ans,xor[j-1 ...

  2. NOI.ac #31 MST DP、哈希

    题目传送门:http://noi.ac/problem/31 一道思路好题考虑模拟$Kruskal$的加边方式,然后能够发现非最小生成树边只能在一个已经由边权更小的边连成的连通块中,而树边一定会让两个 ...

  3. NOI.AC NOIP模拟赛 第五场 游记

    NOI.AC NOIP模拟赛 第五场 游记 count 题目大意: 长度为\(n+1(n\le10^5)\)的序列\(A\),其中的每个数都是不大于\(n\)的正整数,且\(n\)以内每个正整数至少出 ...

  4. NOI.AC NOIP模拟赛 第六场 游记

    NOI.AC NOIP模拟赛 第六场 游记 queen 题目大意: 在一个\(n\times n(n\le10^5)\)的棋盘上,放有\(m(m\le10^5)\)个皇后,其中每一个皇后都可以向上.下 ...

  5. NOI.AC NOIP模拟赛 第二场 补记

    NOI.AC NOIP模拟赛 第二场 补记 palindrome 题目大意: 同[CEOI2017]Palindromic Partitions string 同[TC11326]Impossible ...

  6. NOI.AC NOIP模拟赛 第一场 补记

    NOI.AC NOIP模拟赛 第一场 补记 candy 题目大意: 有两个超市,每个超市有\(n(n\le10^5)\)个糖,每个糖\(W\)元.每颗糖有一个愉悦度,其中,第一家商店中的第\(i\)颗 ...

  7. NOI.AC NOIP模拟赛 第四场 补记

    NOI.AC NOIP模拟赛 第四场 补记 子图 题目大意: 一张\(n(n\le5\times10^5)\)个点,\(m(m\le5\times10^5)\)条边的无向图.删去第\(i\)条边需要\ ...

  8. NOI.AC NOIP模拟赛 第三场 补记

    NOI.AC NOIP模拟赛 第三场 补记 列队 题目大意: 给定一个\(n\times m(n,m\le1000)\)的矩阵,每个格子上有一个数\(w_{i,j}\).保证\(w_{i,j}\)互不 ...

  9. NOI.AC WC模拟赛

    4C(容斥) http://noi.ac/contest/56/problem/25 同时交换一行或一列对答案显然没有影响,于是将行列均从大到小排序,每次处理限制相同的一段行列(呈一个L形). 问题变 ...

随机推荐

  1. 哈哈哈哈,我竟然发现了个MSDN里面的笔误

    typedef __PROCESSOR_INFO {  WORD wVersion;   WCHAR szProcessorCore[40];  WORD wCoreRevision;  WCHAR ...

  2. ubuntu和win10设置双显示器

    ubuntu:最右上角那个图标,点开找到系统设置,系统设置中找到“显示”中,在其中可以调节双屏显示或者只显示一个屏,图等会补... win10:现在是ubuntu系统所以操作忘记了写不出来,等下换系统 ...

  3. 洛谷P1774 最接近神的人_NOI导刊2010提高(02) [2017年6月计划 线段树03]

    P1774 最接近神的人_NOI导刊2010提高(02) 题目描述 破解了符文之语,小FF开启了通往地下的道路.当他走到最底层时,发现正前方有一扇巨石门,门上雕刻着一幅古代人进行某种活动的图案.而石门 ...

  4. Laravel 5.2 使用 JWT 完成多用户认证 | Laravel China 社区 - 高品质的 Laravel 开发者社区 - Powered by PHPHub

    Json Web Token# JWT代表Json Web Token.JWT能有效地进行身份验证并连接前后端. 降地耦合性,取代session,进一步实现前后端分离 减少服务器的压力 可以很简单的实 ...

  5. leetcode 60-80 easy

    66.Plus One Given a non-empty array of digits representing a non-negative integer, plus one to the i ...

  6. 第十章—DOM(一)——Document类型

    DOCUMENT类型 JS通过document类型表示文档,在文档中document对象是HTMLDocument的一个实例,表示整个HTML页面.document对象是window对象的一个属性,因 ...

  7. 杨柳絮-Info:春天将不再漫天飞“雪”,济源治理杨柳絮在行动

    ylbtech-杨柳絮-Info:春天将不再漫天飞“雪”,济源治理杨柳絮在行动 1.返回顶部 1. 天气暖和了,连心情都是阳光的.然而,在这美好的时刻,漫天飞舞的杨柳絮,甚是煞风景.<ignor ...

  8. 下载额外数据文件失败 以下软件包要求安装后下载附加数据,但其数据无法下载或无法处理 ttf-mscorefonts-installer

    故障显示: 一些软件包的数据文件无法下载 以下软件包要求安装后下载附加数据,但其数据无法下载或无法处理. ttf-mscorefonts-installer 这是一个永久错误,系统中的这些软件包将无法 ...

  9. 有意思的DP(CF360B Levko and Array)

    刚才面试了一个蛮有意思的DP题目,脑子断片,没写出来,不过早上状态还是蛮好的 一个长度为n的序列最多改变k次,使相邻两数之差绝对值的最大值最小 三维的dp我先尝试写一下 Codeforces 360B ...

  10. pillow模块

    pillow模块 用于操作图片的模块 安装 pip install pillow 生成验证码 from PIL import Image,ImageDraw,ImageFont from io imp ...