广二集训 day1
T1 线段树合并&dsu on tree
Problem A: 不能说的秘密(secret)
【题目描述】
碎片散落在了一棵有 \(n\) 个顶点的有根树上。顶点从 \(1\) 到 \(n\) 编号,顶点 \(1\) 是根,顶点 \(i\)(\(2 \le i \le n\)) 的父亲编号为 \(p_i\),其中 \(1 \le p_i < i\)。每个顶点上恰有一个碎片,顶点 \(i\) 上的碎片会在第 \(t_i\) 秒末消失。
你试着捡起顶点 \(k\) 子树内的所有碎片。在每一秒,你可以什么也不做,也可以选择一个满足以下条件之一的顶点 \(i\) 并捡起顶点 \(i\) 上的碎片:
- \(i = k\);
- 顶点 \(p_i\) 上的碎片已经被捡起。
每个碎片被捡起的时间都要在其消失之前。例如,对于一个在第 \(3\) 秒末消失的碎片,你需要在第 \(1\) 秒、第 \(2\) 秒或第 \(3\) 秒捡起它。
我们保证了 \(t_i ≥ n\),不难发现你可以随便捡,但是你想拖延时间。试求最迟在第多少秒初开始捡碎片能保证捡完顶点 \(k\) 子树内的所有碎片。
你需要对于 \(k = 1, 2,...,n\) 分别求出答案。
【输入格式】
从文件 secret.in 中读入数据。
输入的第一行包含一个整数 \(n\),表示树的顶点数量。
输入的第二行包含 \(n-1\) 个整数,第 \(i-1\) 个整数 \(p_i\) 表示顶点 \(i\) 的父亲编号。
输入的第三行包含 \(n\) 个整数,第 \(i\) 个整数 \(t-i\) 表示顶点 \(i\) 上的碎片消失时间。
【输出格式】
输出到文件 secret.out 中。
输出一行包含 \(n\) 个整数,第 \(i\) 个整数表示当 \(k = i\) 时的答案。
【样例 1 输入】
6
1 1 2 2 3
6 8 6 7 9 9
1 输出】
4 6 6 7 9 9
线段树合并和启发式合并好题
先说思路,容易发现先按时间排序,然后从小到大依次与根节点打通最优,考虑交换处理顺序,发现肯定会将最小时间提前
我们先处理好操作顺序,按上述描述模拟即可,先处理根为1的
$ans=\min_{i=1}^{n}{t_i-k_i+1} $ , \(k_i\)为操作次序
那我们在每个子树内维护即可
线段树合并
这个处理方法真的很妙,容易发现全局操作次序的相对次序在子树内同样适用,假定线段树底层是操作次序\(k_i\),那么子树内\(k_i\)即为线段树底层节点前面有几个不为空的位置+1
我们考虑线段树合并,容易发现siz是好维护的,再维护一个ans
考虑合并,可以直接继承左子树答案,因为右边已经无值,右子树则需处理,理由如上
\(ans=min(ans_l,ans_r-siz_l)\)
普通线段树合并即可
int insert(int p,int l,int r) {
int id=++cnt;
siz[id]++,val[id]=p;
if(l==r)return id;
int mid=l+r>>1;
if(p<=mid) ls[id]=insert(p,l,mid);
else rs[id]=insert(p,mid+1,r);
return id;
}
void push_up(int p) {
siz[p]=siz[ls[p]]+siz[rs[p]];
val[p]=min(val[ls[p]],val[rs[p]]-siz[ls[p]]);
}
int merge(int x,int y,int l,int r) {
if(!x||!y) return x+y;
if(l==r) {
siz[x]+=siz[y],val[x]=l-siz[x]+1;
return x;
}
int mid=l+r>>1;
ls[x]=merge(ls[x],ls[y],l,mid);
rs[x]=merge(rs[x],rs[y],mid+1,r);
push_up(x);
return x;
}
启发式合并
还是刚才那个思路,我们考虑树上启发式合并
我们还是考虑线段树底层是操作次序\(k_i\),那这样我们需要将\(t_i\)插入进去,用区间减操作处理\(k\)的真实值,具体的讲,将区间\([k_i+1,up]\)都减去1,代表它们在此节点后操作,注意初始线段树来个极大值即可,然后区间取min
时间复杂度为\(O(n log^2 n)\),常数很小
贴一下初绘代码
void pushup(int p) {tr[p] = min(tr[p * 2] , tr[p * 2 + 1]);}
void pushdown(int p) {/* ... */}
void change(int p , int l , int r , int s , int t , int x) {
/*区间加减*/
}
void TJ(int x , int fa , int op) {
change(1 , 1 , n , dis[x] , dis[x] , (a[x].t - M) * op);
change(1 , 1 , n , dis[x] + 1 , n , (-1) * op);
for(auto to : v[x]) {
if(to == fa) continue;
TJ(to , x , op);
}
}
void dfs2(int x , int fa , bool op) {
for(auto to : v[x]) {
if(to == fa || to == son[x]) continue;
dfs2(to , x , 0);
}
if(son[x]) dfs2(son[x] , x , 1);
for(auto to : v[x]) {
if(to == fa || to == son[x]) continue;
TJ(to , x , 1);
}
change(1 , 1 , n , dis[x] , dis[x] , a[x].t - M);//插入自身
change(1 , 1 , n , dis[x] + 1 , n , -1);//区间减,维护子树内k值
ans[x] = tr[1];
if(!op) TJ(x , fa , -1);
}

线段树合并&dsu on tree 好题
浙公网安备 33010602011771号