【LOJ574】「LibreOJ NOI Round #2」黄金矿工(模拟费用流)(链分治)(线段树)

传送门


题解:

很容易看出这道题的费用流模型,由于是在线修改,所以显然我们要支持动态维护流量和费用。

其实模拟费用流最容易犯的错误就是没有考虑退流

我就奇了怪了这题单点修改区间max,怎么没人写ZKW线段树

维护流量还是很简单的,显然我们只需要维护每个点向下流了多少,也就是它能向上反悔多少(显然本身能够向下流的就是无穷大),直接树链剖分支持一下链加即可。

考虑简化一下问题,由于一个矿工和一块黄金匹配产生的贡献是 P x + Q y − d u + d v P_x+Q_y-d_u+d_v Px+Qydu+dv,也就是 ( P x − d u ) + ( Q y + d v ) (P_x-d_u)+(Q_y+d_v) (Pxdu)+(Qy+dv)。我们直接把节点的深度算到矿工和黄金的代价中会方便很多。

考虑加一个矿工,先考虑一直退流他能够走到最高的地方是哪里,那么整个子树内都是他的可达区域,所以我们对子树内黄金需要维护匹配到它会带来多少收益,这里需要同时考虑一下退流。

考虑加一个黄金,显然到根的路径上的点上的矿工都可以和它匹配,同时我们要考虑这条路径外的点能否通过退流走到这条路径上来,维护满足条件的所有点上的矿工匹配产生的收益,同时还是需要考虑一下退流。

然后这道题最麻烦的地方就在于退流了。。。

显然不能直接退,增广路上黄金和矿工数量最坏会是 O ( m ) O(m) O(m)的,考虑二分图匹配的时候翻转一条交错路径,考虑KM算法的复杂度证明就明白了。

所以我们需要通过转化来使得我们并不需要真正去退流

其实考虑如果一个矿工与一块黄金产生匹配,如果这块黄金已经和另一位矿工匹配了,那么这两位矿工实际上是连通的,而这次匹配产生的代价实际上就是让之前匹配的矿工的代价没了,与这块黄金没有任何关系,我们可以看成这两位矿工产生了匹配,代价为第一位矿工的权值减去第二位矿工的权值。

加一块黄金与矿工产生匹配的效果是一样的,和上面同理。

所以我们把匹配转化为:一个代价为 A A A的矿工和一个代价为 B B B的黄金产生匹配,把这次匹配产生的贡献算入答案,然后删掉这位矿工和这块黄金。在原来矿工的位置上放上一个价值为 − A -A A的黄金,在原来黄金的位置上放上一个代价为 − B -B B的矿工。

可以用二分图最大权匹配的退流过程来理解上面这个操作。

于是需要支持的东西就只剩下查找,删除,维护流量了。

然后是一些维护上的细节。

首先线段树+树链剖分维护流量,这个没得说,注意我们需要询问有流量的可达最高点和最低点,区间加区间取min,我选择标记永久化,这个可以在线段树上二分来实现。

对于矿工来说,我们需要找到可达最高点,然后询问子树里面最大代价的黄金,DFS序+线段树维护一下子树最大值,同时每个点开一个set维护一下这个点上黄金的最大值。

对于黄金来说,我们需要把它到根的路径拎出来,然后路径上每个点还需要维护所有退流可达的矿工代价的最大值。直接对于每个矿工暴力跳树链,然后链上每个点维护所有轻儿子的最大值就行了。同样需要开一个set。

由于是单点修改区间max,ZKW线段树非常好写

本题这个做法总共需要开三棵线段树,维护流量,维护黄金和矿工的代价最大值。

目前是LOJ最快AC代码,同时也是最短AC代码和最小内存解法,好了齐了


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;
using pii=std::pair<int,int>;
#define fi first
#define se second
template<class T>T min(T a,T b){return a<b?a:b;}

cs int N=1e5+7,INF=0x7f7f7f7f;
cs pii unit=pii(-INF,0);
int n,m;ll ans;

int el[N],nxt[N<<1],to[N<<1],w[N<<1],ec=1;
inline void adde(int u,int v,int va){
	nxt[++ec]=el[u],el[u]=ec,to[ec]=v,w[ec]=va;
	nxt[++ec]=el[v],el[v]=ec,to[ec]=u,w[ec]=va;
}

pii las[N];
int fa[N],siz[N],son[N],d[N],dis[N];
int in[N],out[N],nd[N],top[N],bot[N],dfn;

void dfs1(int u,int p){
	for(int re e=el[u],v=to[e];e;v=to[e=nxt[e]])
	if(v!=p){
		fa[v]=u;d[v]=d[u]+1,dis[v]=dis[u]+w[e];
		dfs1(v,u),siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}++siz[u];
}
void dfs2(int u,int tp){
	in[u]=++dfn,nd[dfn]=u;top[u]=tp;
	if(son[u]){dfs2(son[u],tp),bot[u]=bot[son[u]];}
	else {bot[u]=u,las[tp]=unit;}
	for(int re e=el[u],v=to[e];e;v=to[e=nxt[e]])
	if(v!=fa[u]&&v!=son[u])dfs2(v,v);out[u]=dfn;
}

class SGT_Tree{
	private:
		int mn[N<<2];
#define lc u<<1
#define rc u<<1|1
		inline void add(int u,int l,int r,int ql,int qr,int v){
			if(ql<=l&&r<=qr){mn[u]+=v;return ;}
			int mid=l+r>>1,tag=mn[u]-min(mn[lc],mn[rc]);
			if(ql<=mid)add(lc,l,mid,ql,qr,v);
			if(mid<qr)add(rc,mid+1,r,ql,qr,v);
			mn[u]=std::min(mn[lc],mn[rc])+tag;
		}
		inline int Ql(int u,int l,int r,int ql,int qr,int ad)cs{
			if(mn[u]+ad>0)return -1;if(l==r)return l;int res=-1;
			int mid=l+r>>1,tag=ad+mn[u]-min(mn[lc],mn[rc]);
			if(ql<=mid)res=Ql(lc,l,mid,ql,qr,tag);
			if(res==-1&&mid<qr)res=Ql(rc,mid+1,r,ql,qr,tag);
			return res;
		}
		inline int Qr(int u,int l,int r,int ql,int qr,int ad)cs{
			if(mn[u]+ad>0)return -1;if(l==r)return l;int res=-1;
			int mid=l+r>>1,tag=ad+mn[u]-min(mn[lc],mn[rc]);
			if(mid<qr)res=Qr(rc,mid+1,r,ql,qr,tag);
			if(res==-1&&ql<=mid)res=Qr(lc,l,mid,ql,qr,tag);
			return res;
		}
	public:
		inline void update(int l,int r,int v){add(1,1,n,l,r,v);}
		inline int Ql(int l,int r)cs{return Ql(1,1,n,l,r,0);}
		inline int Qr(int l,int r)cs{return Qr(1,1,n,l,r,0);}
#undef lc
#undef rc
};
struct ZKW_Tree{
	static cs int N=1<<17|1;
	int M;pii mx[N<<1|1];std::set<pii> st[N];
	void build(int n){
		for(M=1;M<=n+1;M<<=1);
		for(int re i=M;i<=M+n+1;++i)mx[i]=unit;
		for(int re i=M-1;i;--i)mx[i]=max(mx[i<<1],mx[i<<1|1]);
	}
	void update(int p,pii pv,pii v){
		if(pv.fi!=-INF)st[p].erase(pv);if(v.fi!=-INF)st[p].insert(v);
		mx[p+M]=st[p].empty()?pii(-INF,nd[p]):*st[p].rbegin();
		for(p+=M,p>>=1;p;p>>=1)mx[p]=max(mx[p<<1],mx[p<<1|1]);
	}
	pii query(int l,int r){
		pii ans=mx[l+M];
		for(l+=M-1,r+=M+1;l^r^1;l>>=1,r>>=1){
			if(~l&1)ans=max(ans,mx[l^1]);
			if(r&1) ans=max(ans,mx[r^1]);
		}
		return ans;
	}
};

SGT_Tree t1;
ZKW_Tree t2,t3;

void add(int u,int d){
	while(top[u]!=1){
		t1.update(in[top[u]],in[u],d);
		u=fa[top[u]];
	}
	if(u!=1)t1.update(2,in[u],d);
}

void modify(int u){
	while(fa[top[u]]){
		int t=t1.Ql(in[top[u]]+1,in[bot[u]]);
		int v=t==-1?bot[u]:nd[t-1];pii tp;
		if(t1.Ql(in[top[u]],in[top[u]])!=-1)tp=unit;
		else tp=t3.query(in[top[u]],in[v]);
		t3.update(in[fa[top[u]]],las[top[u]],tp);
		las[top[u]]=tp;
		u=fa[top[u]];
	}
}

void update1(int u,int val){
	int v=u;
	for(int re t=u,tp;t;t=fa[top[t]]){
		tp=t1.Qr(in[top[t]],in[t]);
		if(tp!=-1){v=nd[tp];break;}
	}
	pii t=t2.query(in[v],out[v]);
	if(t.fi+val<0){
		t3.update(in[u],unit,pii(val,u));
		modify(u);
	}else {
		ans+=t.fi+val;
		t2.update(in[u],unit,pii(-val,u));
		t2.update(in[t.se],t,unit);
		t3.update(in[t.se],unit,pii(-t.fi,t.se));
		add(u,-1);add(t.se,1);modify(u),modify(t.se);
	}
}

void update2(int u,int val){
	pii p=unit;
	for(int re t=u,v,tp;t;t=fa[top[t]]){
		tp=t1.Ql(in[t]+1,in[bot[t]]);
		v=tp==-1?bot[t]:nd[tp-1];
		p=max(p,t3.query(in[top[t]],in[v]));
	}
	if(p.fi+val<0){
		t2.update(in[u],unit,pii(val,u));
	}
	else {
		ans+=p.fi+val;
		t3.update(in[u],unit,pii(-val,u));
		t3.update(in[p.se],p,unit);
		t2.update(in[p.se],unit,pii(-p.fi,p.se));
		add(p.se,-1);add(u,1);modify(p.se),modify(u);
	}
}

signed main(){
#ifdef zxyoi
	freopen("miner.in","r",stdin);
#endif
	n=gi(),m=gi();
	for(int re i=1;i<n;++i){
		int u=gi(),v=gi(),w=gi();
		adde(u,v,w);
	}
	dfs1(1,0);dfs2(1,1);
	t2.build(n),t3.build(n);
	while(m--){
		int op=gi(),u=gi(),v=gi();
		op==1?update1(u,v-dis[u]):update2(u,v+dis[u]);
		cout<<ans<<"\n";
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值