HDU contest808 ACM多校第7場 Problem - 1008: Traffic Network in Numazu
首先嘚瑟一下這場比賽的排名:59
(第一次看到這麽多 √ emmmm)
好了進入正文QAQ
...這道題啊,思路很清晰啊。
首先你看到樹上路徑邊權和,然後還帶修改,不是顯然可以想到 樹剖+線段樹 維護重鏈麽?
然後你再看啊,這是一個連通圖,然後有 n 個點 n 條邊,於是很顯然會有一個環(然後就構成了一個 仙人掌 ...不過我並不了解仙人掌)
然後你再看!這裏只會有一個環,我們假設沒有這個環,那麽這就是一道 樹剖 模板題,那麽我們可不可以特殊地,讓這個環當根,除這個環以外的其他節點來簡單 樹剖 呢?
恩,這不是顯然麽? 於是我們考慮怎麽處理那個簡單環...emmmm!考慮暴力維護,考慮暴力維護,那麽我們是 O(n)
那麽我們處理一下前綴和呢?很遺憾,這樣只不過是將兩個復雜度反了一下。那麽我們考慮用數據結構優化(中和)這個復雜度。
沒錯,就是樹狀數組維護前綴和,達到查詢和修改都是 O(log n) 的復雜度(並且常數很小),於是這道題成功的包含了 樹剖、線段樹、樹狀數組 這三個算法。
咳咳,別著急啊,這不是還沒說怎麽找環啊。
emmm...相信不用我說,你就一秒想到了 tarjan 。 對啊,顯然啊。
然後就有四個算法了。
還有嗎?(難道還不夠?四個算法除了樹狀數組好大其他碼量大的很啊)
emmm...沒了,真沒了
你真要說有的話,就是標記環時候要用的深搜了...(其實完全可以 tarjan
然後,然後...上代碼(錯誤示範,但思想正確,什麽時候我訂正完了再回來填坑吧)
//by Judge #include<iostream> #include<cstring> #include<cstdio> #define ls k<<1 #define rs k<<1|1 #define mid (l+r>>1) #define ll long long using namespace std; const int M=1e5+111; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } int n,m,pat,cnt,tim,stop; ll t[M<<2],ff[M]; int id[M],s[M],in[M],rk[M]; int head[M],dfn[M],low[M],stk[M],blg[M]; int siz[M],dep[M],f[M],son[M],top[M],val[M]; struct Edge{ int to,val,next,frm; Edge(int to,int val,int next,int frm): to(to),val(val),next(next),frm(frm){} Edge(){} }e[M<<2]; inline void add(int u,int v,int c,int w){ e[++pat]=Edge(v,c,head[u],w),head[u]=pat; e[++pat]=Edge(u,c,head[v],w),head[v]=pat; } #define v e[i].to #define cc e[i].val /* tarjan縮點 處理環 */ void tarjan(int u,int fa){ dfn[u]=low[u]=++tim,stk[++stop]=u; for(int i=head[u];i;i=e[i].next) if(v!=fa){ if(!dfn[v]) tarjan(v,u),low[u]=min(low[u],low[v]); else low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]){ int j=stk[stop]; if(j==u) --stop,blg[u]=u; return ; do{ j=stk[stop--],id[++cnt]=j,in[j]=j; }while(j!=u); for(j=2;j<=cnt;++j) for(int i=head[id[j]];i;i=e[i].next) if(blg[v]!=u) add(u,v,cc,e[i].frm),in[v]=j; } } void dfss(int u,int fa,int stp){ id[stp]=u,cnt=stp,dfn[u]=stp; for(int i=head[u];i;i=e[i].next){ if(blg[v]!=blg[u] || v==fa) continue; if(v!=blg[u]) dfss(v,u,stp+1); in[u]=u; top[v]=blg[v],val[v]=cc,s[e[i].frm]=v; break; } } /* 樹剖建樹 */ void dfs1(int u){ siz[u]=1, dep[u]=dep[f[u]]+1; for(int i=head[u];i;i=e[i].next){ if(blg[v]!=v || v==f[u]) continue; in[u]?in[v]=in[u]:0,f[v]=u,val[v]=cc,dfs1(v); s[e[i].frm]=v,siz[u]+=siz[v],siz[v]>siz[son[u]]?son[u]=v:0; } } void dfs2(int u){ if(!top[u]) top[u]=u; dfn[u]=++tim,rk[tim]=u; if(!son[u]) return; top[son[u]]=top[u],dfs2(son[u]); for(int i=head[u];i;i=e[i].next) if(blg[v]==v && v!=f[u] && v!=son[u]) dfs2(v); } #undef v #undef cc /* binary-indexed-tree */ inline int lowbit(int x){ return x&(-x); } inline void add(int x,int y){ for(;x<=n;x+=lowbit(x)) ff[x]+y; } inline ll get_pre(int x,int y,int z=cnt){ ll r1=0,r2=0; for(;x;x-=lowbit(x)) r1-=ff[x]; for(;y;y-=lowbit(y)) r1+=ff[y]; for(;z;z-=lowbit(z)) r2+=ff[z]; return min(r1,r2-r1); } inline void cbuild(){ for(int i=1,j;i<=cnt;++i) add(i,val[id[i]]); } inline void change(int x,int y){ add(x,-val[id[x]]),val[id[x]]=y,add(x,val[id[x]]); } /* segment-tree */ void build(int k,int l,int r){ if(l==r) return (void)(t[k]=val[rk[l]]); build(ls,l,mid), build(rs,mid+1,r), t[k]=t[ls]+t[rs]; } void update(int k,int l,int r,int x,int y){ if(l>x || r<x) return ; if(l==r) return (void)(t[k]=y); update(ls,l,mid,x,y),update(rs,mid+1,r,x,y),t[k]=t[ls]+t[rs]; } ll query(int k,int l,int r,int L,int R){ if(L>r || l>R) return 0; if(L<=l && r<=R) return t[k]; return query(ls,l,mid,L,R)+query(rs,mid+1,r,L,R); } /* 樹剖詢問 */ inline ll get_sum(int u,int v){ ll ans=0,inu=in[u],inv=in[v]; u=blg[u],v=blg[v]; while(top[u]^top[v]){ if(dep[top[u]]<dep[top[v]]) swap(u,v); ans+=query(1,1,tim,dfn[top[u]],dfn[u]),u=f[top[u]]; } if(u^v){ if(dep[u]>dep[v]) swap(u,v); ans+=query(1,1,tim,dfn[u]+1,dfn[v]); } if(u==id[1]) ans+=get_pre(inu,inv); return ans; } int main(){ freopen("testdata.in","r",stdin); int T=read(),x,y,opt; while(T--){ n=read(),m=read(),pat=cnt=tim=0; for(int i=1,u,v,c;i<=n;++i) in[i]=head[i]=dfn[i]=0, u=read(),v=read(), c=read(),add(u,v,c,i); tarjan(1,0), dfss(id[1],0,1); dfs1(id[1]),dfs2(id[1]); cbuild(),build(1,1,tim); while(m--){ opt=read(),x=read(),y=read(); if(opt) printf("%lld\n",get_sum(x,y)); else if(blg[s[x]]!=id[1]) change(dfn[s[x]],y); else update(1,1,tim,dfn[s[x]],y); } }return 0; }
順便推薦一道我刷了 n 多樹剖模板題還 debug 了半個小時的題目: 月下“毛景樹”,BZOJ 權限題,但洛谷上也有.
這題就是樹剖求路徑上的邊權和,然後帶修改(區間修改!兩個區間修改!),代碼如下,然後有點地方加了解釋,其他都不難理解,模板:
//by Judge
#include<iostream>
#include<cstdio>
#define ls k<<1
#define rs k<<1|1
#define mid (l+r>>1)
using namespace std;
const int M=1e5+111;
const int inf=1e9+7;
inline int read(){
int x=0,f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
inline char cread(){
char c=getchar(); while(!islower(c)) c=getchar(); return c;
}
int n,pat,tim;
int head[M],id[M],s[M],t[M<<2],tag[M<<2],ad[M<<2];
int siz[M],dep[M],f[M],son[M],dfn[M],top[M],val[M];
struct Edge{
int to,val,next;
}e[M<<1];
inline void add(int u,int v,int c){
e[++pat]=(Edge){ v,c,head[u] },head[u]=pat;
e[++pat]=(Edge){ u,c,head[v] },head[v]=pat;
}
#define v e[i].to
void dfs1(int u){
siz[u]=1;
for(int i=head[u];i;i=e[i].next){
if(v==f[u]) continue;
f[v]=u,dep[v]=dep[u]+1,val[v]=e[i].val;
dfs1(v),siz[u]+=siz[v],s[i+1>>1]=v;
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
void dfs2(int u){
dfn[u]=++tim,id[tim]=u;
if(!top[u]) top[u]=u; if(!son[u]) return ;
top[son[u]]=top[u],dfs2(son[u]);
for(int i=head[u];i;i=e[i].next)
if(v!=f[u] && v!=son[u]) dfs2(v);
}
#undef v
inline void build(int k,int l,int r){
if(l==r) return (void)(t[k]=val[id[l]]);
build(ls,l,mid),build(rs,mid+1,r),t[k]=max(t[ls],t[rs]);
}
inline void pushdown(int k){ //pushdown 的時候我們考慮兩個懶標記在任何時候都只會留下一個,於是判斷兩下就好了
if(tag[k]) t[ls]=t[rs]=tag[ls]=tag[rs]=tag[k],tag[k]=ad[ls]=ad[rs]=0;
else if(ad[k]){
if(tag[ls]) t[ls]=tag[ls]+=ad[k]; else ad[ls]+=ad[k],t[ls]+=ad[k];
if(tag[rs]) t[rs]=tag[rs]+=ad[k]; else ad[rs]+=ad[k],t[rs]+=ad[k];
ad[k]=0;
}
}
void update_to(int k,int l,int r,int L,int R,int x){
if(l>R || L>r) return ; if(L<=l && r<=R) return (void)(tag[k]=t[k]=x,ad[k]=0); //修改的話,add標記直接作廢
pushdown(k), update_to(ls,l,mid,L,R,x),update_to(rs,mid+1,r,L,R,x),t[k]=max(t[ls],t[rs]);
}
void update_add(int k,int l,int r,int L,int R,int x){
if(l>R || L>r) return ; if(L<=l && r<=R){ if(tag[k]) t[k]=tag[k]+=x; else ad[k]+=x,t[k]+=x; return ;} //區間加的話,根據是否有修改標記來做
pushdown(k), update_add(ls,l,mid,L,R,x),update_add(rs,mid+1,r,L,R,x),t[k]=max(t[ls],t[rs]);
}
int query(int k,int l,int r,int L,int R){
if(l>R || L>r) return -inf; if(L<=l && r<=R) return t[k];
pushdown(k); return max(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));
}
inline void Change(){
int x=dfn[s[read()]],w=read(); update_to(1,1,tim,x,x,w);
}
inline void Cover(){
int u=read(),v=read(),w=read();
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
update_to(1,1,tim,dfn[top[u]],dfn[u],w),u=f[top[u]];
} if(u==v) return ; if(dep[u]>dep[v]) swap(u,v);
update_to(1,1,tim,dfn[u]+1,dfn[v],w);
}
inline void Add(){
int u=read(),v=read(),w=read();
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
update_add(1,1,tim,dfn[top[u]],dfn[u],w),u=f[top[u]];
} if(u==v) return ; if(dep[u]>dep[v]) swap(u,v);
update_add(1,1,tim,dfn[u]+1,dfn[v],w);
}
inline void Max(){
int u=read(),v=read(),res=-inf;
while(top[u]^top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
res=max(res,query(1,1,tim,dfn[top[u]],dfn[u])),u=f[top[u]];
}
if(u!=v){
if(dep[u]>dep[v]) swap(u,v);
res=max(res,query(1,1,tim,dfn[u]+1,dfn[v]));
} printf("%d\n",res);
}
int main(){
n=read(); int u,v,w,opt;
for(int i=1;i<n;++i)
u=read(),v=read(),w=read(),add(u,v,w);
dep[1]=1,dfs1(1),dfs2(1),build(1,1,tim);
while((opt=cread())!='t')
switch(opt){
case 'h': Change(); break;
case 'o': Cover(); break;
case 'd': Add(); break;
case 'a': Max(); break;
} return 0;
}
刷完這兩道題...路徑邊權和什麽的(以及一些惡心的線段樹雙重懶標記題)都不是問題了吧(理論上)
HDU contest808 ACM多校第7場 Problem - 1008: Traffic Network in Numazu