幻想鄉戰略游戲
傲嬌少女幽香正在玩一個非常有趣的戰略類游戲,本來這個游戲的地圖其實還不算太大,幽香還能管得過來,但是不知道為什么現在的網游廠商把游戲的地圖越做越大,以至於幽香一眼根本看不過來,更別說和別人打仗了。 在打仗之前,幽香現在面臨一個非常基本的管理問題需要解決。 整個地圖是一個樹結構,一共有 \(n\) 塊空地,這些空地被 \(n-1\) 條帶權邊連接起來,使得每兩個點之間有一條唯一的路徑將它們連接起來。
在游戲中,幽香可能在空地上增加或者減少一些軍隊。同時,幽香可以在一個空地上放置一個補給站。 如果補給站在點 \(u\) 上,並且空地 \(v\) 上有 \(d_v\) 個單位的軍隊,那么幽香每天就要花費 \(d_v \cdot \text{dist}(u,v)\) 的金錢來補給這些軍隊。由於幽香需要補給所有的軍隊,因此幽香總共就要花費為 \(\sum (d_v \cdot \text{dist}(u,v))\),其中\(1 \leq v \leq N\))的代價,\(\text{dist}(u,v)\) 表示 \(u\) 個 \(v\) 在樹上的距離(唯一路徑的權和)。 因為游戲的規定,幽香只能選擇一個空地作為補給站。在游戲的過程中,幽香可能會在某些空地上制造一些軍隊,也可能會減少某些空地上的軍隊,進行了這樣的操作以后,出於經濟上的考慮,幽香往往可以移動他的補給站從而省一些錢。但是由於這個游戲的地圖是在太大了,幽香無法輕易的進行最優的安排,你能幫幫她嗎? 你可以假定一開始所有空地上都沒有軍隊。
對於所有數據,\(1 \leq c \leq 1000, \ 0 \leq |e| \leq 1000, \ n \leq 10^5, \ Q \leq 10^5\),並保證所有節點的度數小於等於 \(20\).
分析
考慮答案的形式,發現跟帶權重心非常類似。
假設當前補給站為\(u\),並強制以\(u\)為根,\(v\)為\(u\)的一個子節點,\(\text{sumd}_u\)和\(\text{sumd}_v\)分別為\(u\)的子樹內的\(d\)之和以及\(v\)的子樹內的\(d\)之和,\(\text{len}(u,v)\)為邊\((u,v)\)的長度。
如果將補給站遷移到點\(v\),那么\(v\)的子樹內的點到補給站的距離減少了\(\text{len}(u,v)\),其他的點到補給站的距離增加了\(\text{len}(u,v)\)。也就是說,補給站遷移到點\(v\)時,代價的增量為:
整理一下,得出性質:\(u\)為根,\(v\)為\(u\)的子節點,補給站在\(v\)比\(u\)優,當且僅當:
顯然滿足條件的\(v\)最多只有一個。這時候,如果沒有滿足條件的\(v\),則\(u\)為最優位置。否則最優位置在\(v\)的子樹內。
一個一個跳肯定不行,所以考慮使用點分樹加速跳躍過程。具體而言,如果發現\(u\rightarrow v\)更優的話,那么就遞歸計算\(v\)子樹中的重心,即\(u\)在點分樹上的兒子節點。運用換根DP的知識我們可以維護點分樹上面每個節點子樹的答案並且做到\(O(\log n)\)查詢每個節點換根后的答案。
時間復雜度\(O(n\log n+Q20\log^2 n)\)
動態點分治的作用:在點分治的過程中,一般我們面對的問題都是靜態的。如果涉及到修改這類的操作,我們就希望找到我們是如何處理到當前的修改點的,換而言之,我們希望記錄下點分治的過程,這樣可以通過爬點分樹等操作消除影響。
co int N=2e5+1;
int n,m;
namespace T{ // original tree
vector<pii> e[N];
int lg[N*2],st[N*2][18],dis[N],pos[N],dfn;
void dfs(int u,int fa){
st[pos[u]=++dfn][0]=dis[u];
for(int i=0,v;i<e[u].size();++i){
if((v=e[u][i].first)==fa) continue;
dis[v]=dis[u]+e[u][i].second,dfs(v,u);
st[++dfn][0]=dis[u];
}
}
void init(){
lg[0]=-1;
for(int i=1;i<=n<<1;++i) lg[i]=lg[i>>1]+1;
dfs(1,0),assert(dfn==2*n-1);
for(int j=1;1<<j<=dfn;++j)
for(int i=1;i+(1<<j)-1<=dfn;++i)
st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
int get_dis(int u,int v){
if(pos[u]>pos[v]) swap(u,v);
int k=lg[pos[v]-pos[u]+1];
return dis[u]+dis[v]-2*min(st[pos[u]][k],st[pos[v]-(1<<k)+1][k]);
}
}
int vis[N],sum,root,siz[N],f[N],par[N];
ll down[N],up[N],\text{sumd}[N];
vector<pii> g[N];
void get_root(int u,int fa){
siz[u]=1,f[u]=0;
for(int i=0,v;i<T::e[u].size();++i){
if(vis[v=T::e[u][i].first]||v==fa) continue;
get_root(v,u);
siz[u]+=siz[v],f[u]=max(f[u],siz[v]);
}
f[u]=max(f[u],sum-siz[u]);
if(f[u]<f[root]) root=u;
}
void work(int u,int fa){
vis[u]=1,par[u]=fa;
for(int i=0,v;i<T::e[u].size();++i){
if(vis[v=T::e[u][i].first]) continue;
sum=siz[v],root=0,get_root(v,0);
g[u].push_back(pii(root,v));
work(root,u);
}
}
void ins(int u,int val){
\text{sumd}[u]+=val;
for(int i=u;par[i];i=par[i]){
int dist=T::get_dis(par[i],u);
down[par[i]]+=(ll)dist*val; // underneath ans
up[i]+=(ll)dist*val; // upward transfer
\text{sumd}[par[i]]+=val;
}
}
ll calc(int u){
ll ans=down[u];
for(int i=u;par[i];i=par[i]){
int dist=T::get_dis(par[i],u);
ans+=down[par[i]]-up[i]+dist*(\text{sumd}[par[i]]-\text{sumd}[i]);
}
return ans;
}
ll query(int u){
ll ans=calc(u);
for(int i=0;i<g[u].size();++i)
if(calc(g[u][i].second)<ans)
return query(g[u][i].first);
return ans;
}
int main(){
read(n),read(m);
for(int i=1,u,v,w;i<n;++i){
read(u),read(v),read(w);
T::e[u].push_back(pii(v,w)),T::e[v].push_back(pii(u,w));
}
T::init();
sum=n,f[0]=n,get_root(1,0);
int tmp=root;work(root,0),root=tmp;
for(int u,e;m--;){
read(u),read(e),ins(u,e);
printf("%lld\n",query(root));
}
return 0;
}
將樹二度化之后就不需要點度數\(\leq 20\)的性質了。
時間復雜度\(O(n\log^2 n)\)。
這樣做的本質其實就是二分。
CO int N=4e5+10;
struct edge {int y,w;};
vector<edge> to[N];
namespace Rebuild{
vector<edge> to[N];
void dfs(int x,int fa){
for(int i=0;i<(int)to[x].size();++i){
int y=to[x][i].y;
if(y==fa){
to[x].erase(to[x].begin()+i),--i;
continue;
}
dfs(y,x);
}
}
int main(int n){
for(int i=1;i<n;++i){
int x=read<int>(),y=read<int>(),w=read<int>();
to[x].push_back({y,w}),to[y].push_back({x,w});
}
dfs(1,0);
for(int x=1;x<=n;++x){
if(to[x].size()<=2){
for(CO edge&e:to[x])
::to[x].push_back({e.y,e.w}),::to[e.y].push_back({x,e.w});
continue;
}
int l=++n,r=++n;
::to[x].push_back({l,0}),::to[l].push_back({x,0});
::to[x].push_back({r,0}),::to[r].push_back({x,0});
to[l].assign(to[x].begin(),to[x].begin()+to[x].size()/2);
to[r].assign(to[x].begin()+to[x].size()/2,to[x].end());
}
return n;
}
}
namespace Dist{
int dep[N],pos[N],tim;
int st[2*N][20],lg[2*N];
void dfs(int x,int fa){
pos[x]=++tim,st[tim][0]=dep[x];
for(CO edge&e:to[x])if(e.y!=fa){
dep[e.y]=dep[x]+e.w;
dfs(e.y,x);
st[++tim][0]=dep[x];
}
}
void main(){
dfs(1,0);
lg[0]=-1;
for(int i=1;i<=tim;++i) lg[i]=lg[i>>1]+1;
for(int k=1;k<=lg[tim];++k)for(int i=1;i+(1<<k)-1<=tim;++i)
st[i][k]=min(st[i][k-1],st[i+(1<<(k-1))][k-1]);
}
IN int calc(int x,int y){
if(pos[x]>pos[y]) swap(x,y);
int k=lg[pos[y]-pos[x]+1];
return dep[x]+dep[y]-2*min(st[pos[x]][k],st[pos[y]-(1<<k)+1][k]);
}
}
namespace Seg{
int pos[N],tim,lst[N];
void dfs(int x,int fa){
pos[x]=++tim;
for(CO edge&e:to[x])if(e.y!=fa) dfs(e.y,x);
lst[x]=tim;
}
void main(){
dfs(1,0);
}
int sum[4*N];
#define lc (x<<1)
#define rc (x<<1|1)
#define mid ((l+r)>>1)
void insert(int x,int l,int r,int p,int v){
sum[x]+=v;
if(l==r) return;
if(p<=mid) insert(lc,l,mid,p,v);
else insert(rc,mid+1,r,p,v);
}
int query(int x,int l,int r,int ql,int qr){
if(ql<=l and r<=qr) return sum[x];
if(qr<=mid) return query(lc,l,mid,ql,qr);
if(ql>mid) return query(rc,mid+1,r,ql,qr);
return query(lc,l,mid,ql,qr)+query(rc,mid+1,r,ql,qr);
}
#undef lc
#undef rc
#undef mid
IN void insert(int x,int v){
insert(1,1,tim,pos[x],v);
}
IN int query(int x,int y){
if(pos[x]>pos[y]) return query(1,1,tim,pos[x],lst[x]);
else return sum[1]-query(1,1,tim,pos[y],lst[y]);
}
}
int vis[N],siz[N],all;
pair<int,int> root;
int fa[N];
vector<edge> ch[N];
void find_root(int x,int fa){
siz[x]=1;
pair<int,int> ans={0,x};
for(CO edge&e:to[x])if(!vis[e.y] and e.y!=fa){
find_root(e.y,x);
siz[x]+=siz[e.y];
ans.first=max(ans.first,siz[e.y]);
}
ans.first=max(ans.first,all-siz[x]);
root=min(root,ans);
}
void build(int x){
vis[x]=1;
int old=all;
for(CO edge&e:to[x])if(!vis[e.y]){
root={all=siz[e.y]<siz[x]?siz[e.y]:old-siz[x],0},find_root(e.y,x);
fa[root.second]=x,ch[x].push_back({root.second,e.y});
build(root.second);
}
}
int cen,sum[N];
int64 down[N],up[N];
void insert(int x,int v){
sum[x]+=v;
for(int i=x;fa[i];i=fa[i]){
sum[fa[i]]+=v;
int \text{len}=Dist::calc(x,fa[i]);
down[fa[i]]+=(int64)v*\text{len};
up[i]+=(int64)v*\text{len};
}
}
int64 calc(int x){
int64 ans=down[x];
for(int i=x;fa[i];i=fa[i]){
int \text{len}=Dist::calc(x,fa[i]);
ans+=down[fa[i]]-up[i]+(int64)(sum[fa[i]]-sum[i])*\text{len};
}
return ans;
}
int64 query(int x){
for(CO edge&e:ch[x])
if(2*Seg::query(e.w,x)>sum[cen]) return query(e.y);
return calc(x);
}
int main(){
int n=read<int>(),m=read<int>();
n=Rebuild::main(n);
Dist::main();
Seg::main();
root={all=n,0},find_root(1,0);
cen=root.second;
build(root.second);
while(m--){
int x=read<int>(),v=read<int>();
Seg::insert(x,v);
insert(x,v);
printf("%lld\n",query(cen));
}
return 0;
}
找重心
JKLover種了一棵\(n\)個點的有邊權的樹。他每次會拍拍腦袋想出一個區間\([l,r]\)(長度為奇數),然后心算出編號在這個區間內的點的帶權重心。為了檢驗他算的對不對,他請你寫程序來驗算。
輸入格式
第一行一個整數\(n\)表示樹的點數。
接下來\(n-1\)行,每行三個整數\(x,y,w\)表示一條邊。
下一行一個整數\(m\)表示詢問的次數。
接下來\(m\)行,每行兩個整數\(l,r\)表示一次詢問的區間。
輸出格式
輸出\(m\)行,每行一個整數表示答案。
樣例
5
5 1 3
1 2 5
2 4 5
4 3 1
5
2 4
3 3
1 5
1 3
2 4
4
3
2
2
4
數據范圍與提示
對於15%的數據,滿足\(n\leq 10^3,m\leq 10^3\)。
對於60%的數據,滿足\(m\leq 10^5\)。
對於100%的數據,滿足\(n\leq 5\times 10^4,m\leq 5\times 10^5,w\leq 10^3\)。
請使用較快的輸入輸出方式。
時間限制:1S
空間限制:512MB
題解
注意這道題只需要求出重心,而不需要求出重心到\([l,r]\)的距離和,所以要簡單很多。
那么用可持久化分塊維護DFS序的前綴和,就能做到\(O(n\sqrt{n})+O(m\log n)\)。
可持久化分塊可以把信息存到邊上,比較好實現一些。
區間長度為奇數保證了答案唯一。
CO int N=1e5+10;
struct edge {int y,w;};
vector<edge> to[N];
int from[N];
namespace Rebuild{
vector<edge> to[N];
void dfs(int x,int fa){
for(int i=0;i<(int)to[x].size();++i){
int y=to[x][i].y;
if(y==fa){
to[x].erase(to[x].begin()+i),--i;
continue;
}
dfs(y,x);
}
}
int main(int n){
for(int i=1;i<n;++i){
int x=read<int>(),y=read<int>(),w=read<int>();
to[x].push_back({y,w}),to[y].push_back({x,w});
}
dfs(1,0);
for(int x=1;x<=n;++x) from[x]=x;
for(int x=1;x<=n;++x){
if(to[x].size()<=2){
for(CO edge&e:to[x])
::to[x].push_back({e.y,e.w}),::to[e.y].push_back({x,e.w});
continue;
}
int l=++n,r=++n;
from[l]=from[r]=from[x];
::to[x].push_back({l,0}),::to[l].push_back({x,0});
::to[x].push_back({r,0}),::to[r].push_back({x,0});
to[l].assign(to[x].begin(),to[x].begin()+to[x].size()/2);
to[r].assign(to[x].begin()+to[x].size()/2,to[x].end());
}
return n;
}
}
namespace Block{
int pos[N],tim,idx[N],lst[N];
void dfs(int x,int fa){
pos[x]=++tim,idx[tim]=x;
for(CO edge&e:to[x])if(e.y!=fa) dfs(e.y,x);
lst[x]=tim;
}
CO int B=316;
int root[N],tot;
int ch[N][B+10],sum[N][B+10];
void main(int n){
dfs(1,0);
for(int i=1;i<=n;++i){
int p=pos[i];
int&x=root[i]=root[i-1];
++tot,copy(ch[x]+1,ch[x]+(tim+B-1)/B+1,ch[tot]+1);
copy(sum[x]+1,sum[x]+(tim+B-1)/B+1,sum[tot]+1),x=tot;
for(int i=(p+B-1)/B+1;i<=(tim+B-1)/B;++i) ++sum[x][i];
int&y=ch[x][(p+B-1)/B];
++tot,copy(ch[y]+1,ch[y]+B+1,ch[tot]+1);
copy(sum[y]+1,sum[y]+B+1,sum[tot]+1),y=tot;
for(int i=p-(p-1)/B*B;i<=B;++i) ++sum[y][i];
}
}
IN int query(int i,int p){
int x=root[i];
int ans=sum[x][(p+B-1)/B];
x=ch[x][(p+B-1)/B];
ans+=sum[x][p-(p-1)/B*B];
return ans;
}
IN int query(int l,int r,int x,int y){
if(pos[x]>pos[y]) return query(r,lst[x])-query(r,pos[x]-1)-(query(l-1,lst[x])-query(l-1,pos[x]-1));
else return r-l+1-(query(r,lst[y])-query(r,pos[y]-1)-(query(l-1,lst[y])-query(l-1,pos[y]-1)));
}
}
int vis[N],siz[N],all;
pair<int,int> root;
vector<edge> ch[N];
void find_root(int x,int fa){
siz[x]=1;
pair<int,int> ans={0,x};
for(CO edge&e:to[x])if(!vis[e.y] and e.y!=fa){
find_root(e.y,x);
siz[x]+=siz[e.y];
ans.first=max(ans.first,siz[e.y]);
}
ans.first=max(ans.first,all-siz[x]);
root=min(root,ans);
}
void build(int x){
vis[x]=1;
int old=all;
for(CO edge&e:to[x])if(!vis[e.y]){
root={all=siz[e.y]<siz[x]?siz[e.y]:old-siz[x],0},find_root(e.y,x);
ch[x].push_back({root.second,e.y});
build(root.second);
}
}
int query(int x,int l,int r){
for(CO edge&e:ch[x])
if(2*Block::query(l,r,e.w,x)>r-l+1) return query(e.y,l,r);
return x;
}
int main(){
freopen("centroid.in","r",stdin),freopen("centroid.out","w",stdout);
int n=read<int>();
all=Rebuild::main(n);
Block::main(n);
root={all,0},find_root(1,0);
int cen=root.second;
build(root.second);
for(int m=read<int>();m--;){
int l=read<int>(),r=read<int>();
writeln(from[query(cen,l,r)]);
}
return 0;
}