您的位置:首页 > 其它

【BZOJ 3924】[Zjoi2015]幻想乡战略游戏

2017-12-08 19:10 411 查看

题目:

  


题解:

  对点分树理解加深了233,膜拜zzh干翻紫荆花。

  感谢zzh的讲解。

  首先优化基于传统DP,假设树不发生变化,我们就可以利用DP求出带权重心。

  考虑修改,我们思路不变,还是从root开始找,但发现这样会被卡成$n^2$,原因是每次经过点太多,为了优化,考虑点分树,由于点分树的性质使得假设我们可以在点分树上找到最优解,那么每次最多经过$log$个节点,可以保证时间复杂度。

  然后考虑在点分树转移,假设当前节点为x,我们枚举其在原树中的边,假设当前枚举边的另一端为y,那么由DP可以得出如果以当前边分为两半,若y的一半点权和大于所有点权的一半,那么最优解一定在y那边存在,然后我们由点分树直接跳跃到y对应的块中。若不存在这样的y,则x一定为最优解。

  这样的话我们的目的就是求x点对应的答案以及y一边对应的点权和,我们用三个数组来记录当前x的点分子树的点权和,点分子树到达x的$d*dis$和,以及到达其父亲的$d*dis$和,这样统计x的答案就可以在$log$的时间内完成。

  对于y我们可以开一个$log$大小的数组来记录在点分数上走过的点,并按照原树dfs排序,每次到达一个新的x用$log$更新,并在此序列上维护一个$sum$表示经过路径上x与其儿子s点权和之差,那么考虑若枚举的y是x在原树的儿子或父亲时的情况,分类讨论,利用$sum$快速求出y一边的点权和。

  综上所述,时间复杂度为$O(20nlog_2^nlog_2^{log_2^n})$

代码:

#define Troy
#define inf 0x7fffffff

#include "bits/stdc++.h"

using namespace std;

inline int read(){
int s=0,k=1;char ch=getchar();
while (ch<'0'|ch>'9')    ch=='-'?k=-1:0,ch=getchar();
while (ch>47&ch<='9')    s=s*10+(ch^48),ch=getchar();
return s*k;
}

const int N=1e5+5;

typedef long long ll;

struct edges {
int v,nv,w;edges *last;
}edge[N<<1],*head
;int cnt;

inline void push(int u,int v,int w){
edge[++cnt]=(edges){v,0,w,head[u]};head[u]=edge+cnt;
}

int bit[30];

class ST{
public:
inline void build(int *a,int n){
lgs[0]=-1;
register int i,j;
for (i=1;i<=n;++i)  lgs[i]=lgs[i>>1]+1,f[i][0]=a[i];
for (i=1;bit[i]<=n;++i)
for (j=1;j+bit[i]<=n+1;++j)
f[j][i]=min(f[j][i-1],f[j+bit[i-1]][i-1]);
}

inline int query(int l,int r){
if(r<l) swap(l,r);int t=lgs[r-l+1];
return min(f[l][t],f[r-bit[t]+1][t]);
}
private:
int f[N<<1][20],lgs[N<<1];
}RMQ;

int dis
,eular[N<<1],num,beg
,End
,n,m;

inline void DFS(int x,int fa){
eular[beg[x]=++num]=dis[x];
for(edges *i=head[x];i;i=i->last)   if(i->v^fa){
dis[i->v]=dis[x]+i->w;
DFS(i->v,x);
eular[++num]=dis[x];
}End[x]=num;
}

inline ll get_dis(int x,int y){
return dis[x]+dis[y]-(RMQ.query(beg[x],beg[y])<<1);
}

class Point_Divide_Tree{
public:
int root,tot,fat
,size
,heavy
,dsum
;
bool vis
;
ll dissum
,fdissum
;

inline void dfs(int x,int fa){
size[x]=1,heavy[x]=0;
for(edges *i=head[x];i;i=i->last)   if(i->v!=fa&&!vis[i->v]){
dfs(i->v,x),size[x]+=size[i->v];
heavy[x]=max(heavy[x],size[i->v]);
}heavy[x]=max(heavy[x],tot-size[x]);
if(heavy[root]>heavy[x])    root=x;
}

inline void build(int x,int fa){
root=0,dfs(x,0);
vis[x=root]=true,fat[x]=fa,dfs(x,x);
for (edges *i=head[x];i;i=i->last) if(!vis[i->v]){
tot=size[i->v];
build(i->v,x),i->nv=root;
}root=x;
}

inline void insert(int x,int y,int val){
tot+=val;
while(x){
dsum[x]+=val;
dissum[x]+=get_dis(x,y)*val;
if(fat[x])
fdissum[x]+=get_dis(fat[x],y)*val;
x=fat[x];
}
}

ll ans,sum[30];
int pos[30],leth;

inline int calc(int fa,int x,int real){
int ret=dsum[real];
if(dis[x]<dis[fa]){
int l=lower_bound(pos+1,pos+leth+1,beg[fa])-pos,
r=upper_bound(pos+1,pos+leth+1,End[fa])-pos-1;
ret+=sum[leth]-sum[r]+sum[l-1];
}else{
int l=lower_bound(pos+1,pos+leth+1,beg[x])-pos,
r=upper_bound(pos+1,pos+leth+1,End[x])-pos-1;
ret+=sum[r]-sum[l-1];
}
return ret;
}

inline ll calc(int x){
ll ret=dissum[x];
int p=x;
while(fat[x]){
ret+=(dsum[fat[x]]-dsum[x])*get_dis(fat[x],p)+dissum[fat[x]]-fdissum[x];
x=fat[x];
}return ret;
}

inline void update(int x,int y){
ll now=dsum[x]-dsum[y];
for(int i=leth+1;i;--i){
sum[i]=sum[i-1]+now;
if(i==1||pos[i-1]<=beg[x]){
pos[i]=beg[x];
break;
}else   pos[i]=pos[i-1];
}++leth;
}

inline void query(int x){
for(edges *i=head[x];i;i=i->last) if(i->nv){
if(calc(x,i->v,i->nv)*2>=tot){
update(x,i->nv);
ans=calc(i->nv);
query(i->nv);
break;
}
}
}

inline void query(){
leth=0;
ans=dissum[root];
query(root);
printf("%lld\n",ans);
}
}tree;

int main(){
register int i,j;
for (i=0;i<=20;++i)  bit[i]=1<<i;
n=read(),m=read();
for (i=1;i^n;++i){
int a=read(),b=read(),c=read();
push(a,b,c),push(b,a,c);
}
DFS(1,1),RMQ.build(eular,num);
tree.tot=n,tree.heavy[0]=inf;
tree.build(1,0),tree.tot=0;
while(m--){
i=read(),j=read();
tree.insert(i,i,j);
tree.query();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: