传送门
题解:
很容易看出这道题的费用流模型,由于是在线修改,所以显然我们要支持动态维护流量和费用。
其实模拟费用流最容易犯的错误就是没有考虑退流
我就奇了怪了这题单点修改区间max,怎么没人写ZKW线段树
维护流量还是很简单的,显然我们只需要维护每个点向下流了多少,也就是它能向上反悔多少(显然本身能够向下流的就是无穷大),直接树链剖分支持一下链加即可。
考虑简化一下问题,由于一个矿工和一块黄金匹配产生的贡献是 P x + Q y − d u + d v P_x+Q_y-d_u+d_v Px+Qy−du+dv,也就是 ( P x − d u ) + ( Q y + d v ) (P_x-d_u)+(Q_y+d_v) (Px−du)+(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;
}