2025.4.6 L题单做题
受 kenma 影响,尝试每天花一些时间写博客回顾学过的内容,用开摆时间换博客时间。
P5386 [Cnoi2019] 数字游戏
和秃子酋长一类的题,转化成保留 \([x,y]\) 之间的数后连通块的权值和,权值定义为 \(n\times (n+1)\over 2\)。
发现可以莫队,需要动态查询前驱后继,使用链表维护即可。
关于前驱后继类问题:
只删除,只查询未删除的位置:链表
只删除,查询 \(n\) 个可能的位置:并查集(使用压位可以去掉 \(\alpha\))
无性质,值域较小:压位 Trie
无性质,操作与查询不同阶:值域分块
无性质,操作与查询同阶;或单纯为了代码简略:set
P11394 [JOI Open 2019] 病毒实验
神秘图论。
首先一个点被感染,周围四联通的点只有 \(16\) 种可能的情况,先干掉字符串的限制。
然后变为求 SCC 的出度为 \(0\) 点的 \(\min siz\)。
维护 SCC 不好做,考虑其必要条件,使用代表元思想,用内向生成树表示 SCC,根代表 SCC 中的一个点。
使用类似 Boruvka 的思想,每次合并两个连通块,若 \(rt_A\) 能走到 \(rt_B\) 则说明 \(rt_A\) 没有用,需要将 \(A\) 中所有点放入 \(B\) 中。
合并到无法合并后,对每个 \(rt_i\) 暴力求出答案。
启发:
类似 dp 非最优不转移思想,使用必要条件可以简化问题。
写挂原因:
对于 Boruvka 理解不深刻,不能强行使用一些简单低级的方法来代替高级的结构。比如本题:我尝试使用简单的指针来代替并查集完成一系列操作,显然是不可能的。
不要尝试对模板进行一些分讨爆改,减少分讨量才能尽可能避免写挂。
「LibreOJ NOI Round #2」黄金矿工
模拟费用流,超级吃屎题,人员调度严格加强版。
动态加边费用流,使用一种特殊的消圈方法:将匹配的黄金权值取负后也看作矿工,将匹配的矿工权值取负后看作黄金。手模之后发现可以实现重新匹配。
树链剖分常用技巧:对于一个点,维护它自身和轻儿子的信息。
写挂原因:
树剖分讨漏了几种情况。
修改和查询:都需要对轻边和重边分讨,跳轻边时不要漏掉重儿子的信息
贡献增减的好写方法:只要产生修改,先撤销全部贡献,再重算新的贡献,多个修改一个一个来,不要写揉在一起,写吃屎分讨
撤销贡献的好方法:撤销必须栈序,如果是在线撤销,不要写栈,使用递归实现
展示我高贵的 \(6.5KB\) 的代码
///超级无敌炫酷吃屎大赛,现在是8:30,我开始吃屎了,希望11:00能吃完。
///现在是10:35,写完了,但是肯定吃不完了,争取12:00能吃完。
///现在是10:48,过样例了,爆蛋了
///现在是10:55,48分了
///现在是11:30,52分了
///现在是11:45,拍出来了
///现在是11:55,100分,吃饱了,用时3小时5分钟。
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define int long long
const int N=1e5+5;
const int inf=1e18;
int n,m;
int head[N],len;
struct E{
int to,next,w;
}e[N*2];
void add(int u,int v,int w){e[++len]=E{v,head[u],w};head[u]=len;}
int dep[N],siz[N],son[N],dfn[N],te,to[N],top[N],fa[N],ed[N];
int dp[N];
void dfs1(int u,int ft){
dep[u]=dep[ft]+1;
siz[u]=1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to,w=e[i].w;
if(v==ft) continue;
dp[v]=dp[u]+w;
dfs1(v,u);
siz[u]+=siz[v];fa[v]=u;
if(siz[son[u]]<siz[v]) son[u]=v;
}
}
void dfs2(int u,int ft){
dfn[u]=++te;to[te]=u;
top[u]=(u==son[ft]? top[ft]: u);
if(son[u]) dfs2(son[u],u),ed[u]=ed[son[u]];
else ed[u]=u;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==ft||v==son[u]) continue;
dfs2(v,u);
}
}
const int S=3e5+5;
struct Seg_1{//维护子树黄金最值
pii mx[S];
priority_queue<int> q[S];
void up(int u){mx[u]=max(mx[u<<1],mx[(u<<1)|1]);}
void leaf(int u,int x){
mx[u]={(q[u].size()? q[u].top(): -inf),to[x]};
}
void build(int s,int t,int u){
if(s==t){leaf(u,s);return;}
int mid=(s+t)>>1;
build(s,mid,u<<1);build(mid+1,t,(u<<1)|1);
up(u);
}
void add(int s,int t,int u,int x,int v,int op){
if(s==t){
if(!op) assert(q[u].size()),q[u].pop();
else q[u].push(v);
leaf(u,s);
return;
}
int mid=(s+t)>>1;
if(x<=mid) add(s,mid,u<<1,x,v,op);
else add(mid+1,t,(u<<1)|1,x,v,op);
up(u);
}
pii query(int l,int r,int s,int t,int u){
if(l<=s&&t<=r) return mx[u];
int mid=(s+t)>>1;
pii res={-inf,0};
if(l<=mid) res=max(res,query(l,r,s,mid,u<<1));
if(r>mid) res=max(res,query(l,r,mid+1,t,(u<<1)|1));
return res;
}
}s1;
struct Seg_2{
int ad[S];
pii f1[S],f2[S];
void revise(int u,int k){
ad[u]+=k;
f1[u].first+=k;f2[u].first+=k;
}
void pushdown(int u){
if(ad[u]){
auto fn=[&](int x)->void{
revise(x,ad[u]);
};
fn(u<<1);fn((u<<1)|1);
ad[u]=0;
}
}
void up(int u){
f1[u]=min(f1[u<<1],f1[(u<<1)|1]);
f2[u]=min(f2[u<<1],f2[(u<<1)|1]);
}
void build(int s,int t,int u){
if(s==t){f1[u]={0,s};f2[u]={0,-s};return;}
int mid=(s+t)>>1;
build(s,mid,u<<1);build(mid+1,t,(u<<1)|1);
up(u);
}
void update(int l,int r,int s,int t,int u,int vl){
if(l<=s&&t<=r){revise(u,vl);return;}
pushdown(u);
int mid=(s+t)>>1;
if(l<=mid) update(l,r,s,mid,u<<1,vl);
if(r>mid) update(l,r,mid+1,t,(u<<1)|1,vl);
up(u);
}
pii query(int l,int r,int s,int t,int u,int op){
if(l<=s&&t<=r) return (op==1? f1[u]: f2[u]);
int mid=(s+t)>>1;
pushdown(u);
pii res={inf,0};
if(l<=mid) res=min(res,query(l,r,s,mid,u<<1,op));
if(r>mid) res=min(res,query(l,r,mid+1,t,(u<<1)|1,op));
return res;
}
}s2;
struct Heap{
priority_queue<pii> q1,q2;
unsigned size(){return q1.size()-q2.size();}
void push(pii x){q1.push(x);}
void del(pii x){q2.push(x);}
pii top(){while(q2.size()&&q1.top()==q2.top()) q1.pop(),q2.pop();return q1.top();}
void pop(){top();q1.pop();}
};
struct Seg_3{
Heap q[S];
pii mx[S];
void up(int u){mx[u]=max(mx[u<<1],mx[(u<<1)|1]);}
void leaf(int u){
if(q[u].size()) mx[u]=q[u].top();
else mx[u]={-inf,0};
}
void build(int s,int t,int u){
if(s==t){leaf(u);return;}
int mid=(s+t)>>1;
build(s,mid,u<<1);build(mid+1,t,(u<<1)|1);
up(u);
}
void add(int s,int t,int u,int x,pii v,int op){
if(s==t){
if(!op) q[u].del(v);
else q[u].push(v);
leaf(u);return;
}
int mid=(s+t)>>1;
if(x<=mid) add(s,mid,u<<1,x,v,op);
else add(mid+1,t,(u<<1)|1,x,v,op);
up(u);
}
pii query(int l,int r,int s,int t,int u){
if(l<=s&&t<=r) return mx[u];
int mid=(s+t)>>1;
pii res={-inf,0};
if(l<=mid) res=max(res,query(l,r,s,mid,u<<1));
if(r>mid) res=max(res,query(l,r,mid+1,t,(u<<1)|1));
return res;
}
}s3;
int ans;
int jump_1(int u){//矿工可达区域
while(u){
int x=top[u];
pii rs=s2.query(dfn[x],dfn[u],1,n,1,2);
rs.second=to[-rs.second];
if(!rs.first) return rs.second;
u=fa[x];
}
return 1;
}
int lca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=fa[top[u]];
}
return dep[u]<dep[v]? u: v;
}
void add_5(int u,int op){//维护轻儿子贡献
if(top[u]==1) return;
int x=top[u];
auto fn=[&]()->void{
pii p=s2.query(dfn[x],dfn[ed[x]],1,n,1,1);
p.second=to[p.second];
int d=p.first? ed[x]: fa[p.second];
if(dfn[x]<=dfn[d]){
pii v=s3.query(dfn[x],dfn[d],1,n,1);
if(v.first>-inf) s3.add(1,n,1,dfn[fa[x]],v,op);
}
};
if(op) fn();
add_5(fa[x],op);
if(!op) fn();
}
void jump_2(int u,int v,int op){//一组匹配
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
s2.update(dfn[top[u]],dfn[u],1,n,1,op);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
if(u==v) return;
s2.update(dfn[u]+1,dfn[v],1,n,1,op);
}
void add_6(int u,int v){//新增匹配
int g=lca(u,v);
add_5(u,0);
jump_2(u,g,-1);
add_5(u,1);
add_5(v,0);
jump_2(v,g,1);
add_5(v,1);
}
void add_3(int u,int x,int op){//插入/删除矿工
add_5(u,0);
s3.add(1,n,1,dfn[u],{x,u},op);
add_5(u,1);
}
void add_4(int u,int x,int op){//插入/删除黄金
s1.add(1,n,1,dfn[u],x,op);
}
void add_1(int u,int x){//新增矿工
int u0=u;
u=jump_1(u);
pii mx=s1.query(dfn[u],dfn[u]+siz[u]-1,1,n,1);
if(mx.first+x>0){
ans+=mx.first+x;
add_4(mx.second,mx.first,0);
add_3(mx.second,-mx.first,1);
add_4(u0,-x,1);
add_6(u0,mx.second);
}
else add_3(u0,x,1);
}
void add_2(int u,int x){
int u0=u;
pii mx={-inf,0};
auto get=[&](int u){
if(u!=ed[u]){
pii p=s2.query(dfn[u]+1,dfn[ed[u]],1,n,1,1);
p.second=to[p.second];
int d=p.first? ed[u]: fa[p.second];
mx=max(mx,s3.query(dfn[u],dfn[d],1,n,1));
}
};
while(u){
get(u);
int x=top[u];
mx=max(mx,s3.query(dfn[x],dfn[u],1,n,1));
u=fa[x];
}
if(mx.first+x>0){
ans+=mx.first+x;
add_3(mx.second,mx.first,0);
add_4(mx.second,-mx.first,1);
add_3(u0,-x,1);
add_6(mx.second,u0);
}
else add_4(u0,x,1);
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<n;i++){
int u,v,w;cin>>u>>v>>w;add(u,v,w);add(v,u,w);
}
dfs1(1,0);
dfs2(1,0);
s1.build(1,n,1);
s2.build(1,n,1);
s3.build(1,n,1);
for(int i=1;i<=m;i++){
int op,u,x;cin>>op>>u>>x;
if(op==1){
x-=dp[u];
add_1(u,x);
}
else{
x+=dp[u];
add_2(u,x);
}
cout<<ans<<'\n';
}
return 0;
}