P3345 [ZJOI2015] 幻想乡战略游戏
神秘题,细节虽然不多但必须完全理解每个东西在做什么。
可以看代码,附带注释,这里讲一下大致的。
先随便钦定一个点为重心,发现如果原树重心不是它,则一定存在一个儿子,移动到它儿子更优,即 \(siz_x-siz_{son} -siz_{son}\) 为负数,就是 \(2\times siz_{son} > siz_x\),直接跑就好了。
题目又说度数 \(\le 20\),那么直接搞复杂度跟树高挂钩,单次为 \(20\times L\)。
什么,跟树高挂钩?考虑树上常用的东西--点分树。
考虑点分树,点分树树高是 \(\log\) 的,然后处理一下就好了。
有人会说了:what,点分树树都跟原来的差了十万八千里了,还能做?
是的,还是可以的,考虑当前在点 \(i\),点分树上连了个点 \(j\),但实际上这颗子树在原树上与 \(i\) 连边的点是 \(z\),那只要 \(z\) 比 \(i\) 优,那答案不就是在原树 \(z\) 的子树,点分树 \(j\) 的子树里吗(两个等价),那就可以转了。
注意啊,由 \(i\) 到 \(j\) 时要给 \(z\) 挂上一个额外贡献,然后更新 \(z\) 到 \(j\) 的值,之后在减去就好了。
还不懂的话,我想想,建议画图理解,如果点分树有问题建议先去看模版搞透了来。
代码有部分注释方便理解。
#include<bits/stdc++.h>
#define int long long
using namespace std;
#define getchar() (p1 == p2 && (p2 = (p1 = buf1) + fread(buf1, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf1[1 << 23], *p1 = buf1, *p2 = buf1, ubuf[1 << 23], *u = ubuf;
namespace IO
{
template<typename T>
void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
template<typename T,typename... Args>
void read(T &_x,Args&...others){Read(_x);Read(others...);}
const int BUF=20000000;char buf[BUF],to,stk[32];int plen;
#define pc(x) buf[plen++]=x
#define flush(); fwrite(buf,1,plen,stdout),plen=0;
template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++to]=48+x%10;while(to) pc(stk[to--]);}
}
using namespace IO;
const int N = 2e5+10;
int n,q,x,y,z,a[N],head[N],v[N],cnt,mx[N],siz[N],xn,fa[N],cnt1,dis[N];
int dfn[N],st[N][19],lg[N],fa1[N];//求lca
int zx,mx1;//重心和最大值小的
int D;//根节点
vector<int>v1[N];//每个点对应的儿子(对于点分树上来讲)
vector<int>v2[N];//每个点对应的儿子(原树)
int sum[N],sum1[N];//a_i的和考虑该子树到父亲的花费(若没父亲就设为到自己)
struct w
{
int to,nxt,z;
}b[N<<1];
inline void add(int x,int y,int z)
{
b[++cnt].nxt = head[x];
b[cnt].to = y; b[cnt].z = z;
head[x] = cnt;
}
inline int Min(int x,int y)//返回深度更小的,显然深度小的dfn也小,写dep也行
{
if(dfn[x] < dfn[y]) return x;
return y;
}
inline int dist(int x,int y)//求两点距离,用的dfs序求lca,厉害吧
{
if(x == y) return 0;
int X = x,Y = y;
x = dfn[x],y = dfn[y];
if(x > y) swap(x,y);
x++; int k = lg[y-x+1];
int o1 = Min(st[x][k],st[y-(1<<k)+1][k]);
return dis[X]+dis[Y]-2*dis[o1];
}
void dfs(int x,int y)//找重心
{
siz[x] = 1,mx[x] = 0;
for(int i = head[x];i;i = b[i].nxt)
if(!v[b[i].to] && b[i].to != y)
dfs(b[i].to,x),mx[x] = max(mx[x],siz[b[i].to]),siz[x] += siz[b[i].to];
mx[x] = max(mx[x],xn-siz[x]);
if(mx[x] < mx1) zx = x,mx1 = mx[x];
}
void dfs1(int x,int y)
{
siz[x] = 1;
for(int i = head[x];i;i = b[i].nxt)
if(!v[b[i].to] && b[i].to != y)
dfs1(b[i].to,x),siz[x] += siz[b[i].to];
}
void dfs2(int x,int y)
{
siz[x] = 1; dfn[x] = ++cnt1; fa1[x] = y;
for(int i = head[x];i;i = b[i].nxt)
if(b[i].to != y)
dis[b[i].to] = dis[x]+b[i].z,dfs2(b[i].to,x),siz[x] += siz[b[i].to];
}
inline void solve(int x)//构建点分树
{
v[x] = 1;
for(int i = head[x];i;i = b[i].nxt)
if(!v[b[i].to])
{
mx1 = 1e9; xn = siz[b[i].to]; dfs(b[i].to,x);
v1[x].push_back(zx); v2[x].push_back(b[i].to);
fa[zx] = x; dfs1(zx,0); solve(zx);
}
}
void change(int x,int y)//加入
{
a[x] += y;
for(int i = x;i;i = fa[i])
{
sum[i] += y;
if(fa[i]) sum1[i] += y*dist(x,fa[i]);
else sum1[i] += y*dist(x,i);
}
}
int query(int x)
{
int ans = 0,S = 0,S1 = sum[x];
for(int i = 0;i < v1[x].size();i++) S += sum1[v1[x][i]];
for(int i = 0;i < v1[x].size();i++)
if(sum[v1[x][i]]*2 > sum[x])//转移过去更优
{
int o = S1-sum[v1[x][i]];
ans = S-sum1[v1[x][i]]+(S1-sum[v1[x][i]])*dist(x,v2[x][i]);//这些到v2_{x,i}的距离
a[v2[x][i]] += o;
for(int j = v2[x][i];j != x;j = fa[j])
{
sum[j] += o;
if(fa[j]) sum1[j] += o*dist(v2[x][i],fa[j]);
else sum1[j] += o*dist(v2[x][i],j);
}//将这些挂在v2_{x,i}上面,然后继续跑,之后删掉就好了
ans += query(v1[x][i]);
for(int j = v2[x][i];j != x;j = fa[j])
{
sum[j] -= o;
if(fa[j]) sum1[j] -= o*dist(v2[x][i],fa[j]);
else sum1[j] -= o*dist(v2[x][i],j);
}
a[v2[x][i]] -= o;
return ans;//显然只会存在一个
}
return S;//没有就返回子树到它的值
}
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n),read(q);
for(int i = 1;i < n;i++) read(x),read(y),read(z),add(x,y,z),add(y,x,z);
mx1 = 1e9,xn = n; dfs(1,0); D = zx;
dfs2(zx,0);
solve(zx);
for(int i = 2;i <= n;i++) lg[i] = lg[i/2]+1;
for(int i = 1;i <= n;i++) st[dfn[i]][0] = fa1[i];//注意dfs序初值是这样赋的
for(int i = 1;i <= lg[n];i++)
for(int j = 1;j+(1<<i)-1 <= n;j++)
st[j][i] = Min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
while(q--)
{
read(x),read(y);//a[x]+=y
change(x,y);
print(query(D)),pc('\n');//D是根节点
}
flush();
return 0;
}
/*
警告,滥用本题者将被封号。这么牛的题,更要做了
考虑dp,度数<=20,暴力找y,sum_y*2 > sum_x 那么答案就在y子树内(x到y会加上sum_x-sum_y - sum_y 的贡献)
所以此题跟树高有关系,考虑点分树,树log树高
维护一个sum_x表示a_i和,sum1_x表示每个x子树内的点z到fa_x的dis(z,fa_x)*a_z总和
用dfs序求lca,关键结论:父亲dfn比儿子dfn小
*/
浙公网安备 33010602011771号