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;
}
posted @ 2025-07-16 21:29  born_to_sun  阅读(11)  评论(0)    收藏  举报