4712: 洪水 基于链分治的动态DP

时间:2023-03-09 00:32:09
4712: 洪水 基于链分治的动态DP

国际惯例的题面:
4712: 洪水 基于链分治的动态DP
看起来很神的样子......如果我说这是动态DP的板子题你敢信?
基于链分治的动态DP?说人话,就是树链剖分线段树维护DP。
既然是DP,那就先得有转移方程。
我们令f[i]表示让i子树中的叶子节点全部与根不联通,所需要的最小代价,v[i]为输入的点权。
显然f[i]=min(v[i],sigma(f[soni])),边界条件是,如果i是叶子节点,则f[i]=v[i]。
我们需要用链分治去维护这个DP,所以要把DP拆成重链和轻链独立的形式。
我们还是用f[i]表示让i子树中的叶子节点全部与根不联通,所需要的最小代价,h[i]表示i的轻儿子的f值之和。
根据定义,h[i]=sigma(h[light_sons_i]),
同时我们有:f[i]=min(v[i],f[heavy_son_i]+h[i])。
这个转移我们能写成一个最短路矩阵相乘的形式:
{0,f[heavy_son_i]}*{{0,v[i]},{inf,h[i]}} = {0,f[i]}
(我的写法和C++多维数组的表示方法相同,inf表示不能转移)
显然(陈俊锟说过)这个矩阵连乘是具有结合性的,所以我们能用线段树维护一条链上转移矩阵的连乘积。
于是修改的时候,我们只需要从这个点一路改上去,在跳轻边的时候修改其父亲的h值和转移矩阵,然后线段树更新即可。
查询的话就查询这个点到其所在重链底端线段树上转移矩阵的连乘积,然后左乘一个初始化矩阵即可。
时间复杂度O(8nlog^2n),加上fread快读还是不慢(能看)的。
(其实分析题目性质能够让转移复杂度更低,然而不如矩阵的方法通用(反正就是个常数,不管他了)。关于分析性质的解法,见我BZOJ5210的题解)
代码:

 #pragma GCC optimize(2)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
typedef long long int lli;
const int maxn=2e5+1e2;
const lli inf=0x3f3f3f3f3f3f3f3fll; struct Matrix {
lli dat[][];
Matrix() { memset(dat,0x3f,sizeof(dat)); }
lli* operator [] (const int &x) { return dat[x]; }
const lli* operator [] (const int &x) const { return dat[x]; }
friend Matrix operator * (const Matrix &a,const Matrix &b) {
Matrix ret;
for(int i=;i<;i++) for(int j=;j<;j++) for(int k=;k<;k++) ret[i][j] = std::min( ret[i][j] , a[i][k] + b[k][j] );
return ret;
}
}trans[maxn],ini; lli v[maxn],f[maxn],h[maxn];
int s[maxn],t[maxn<<],nxt[maxn<<];
int fa[maxn],siz[maxn],dep[maxn],son[maxn],top[maxn],id[maxn],rec[maxn],mxd[maxn],iid;
int n; struct SegmentTree {
Matrix dat[maxn<<];
#define lson(pos) (pos<<1)
#define rson(pos) (pos<<1|1)
inline void build(int pos,int l,int r) { // Warning :: from right to left .
if( l == r ) return void(dat[pos]=trans[rec[l]]);
const int mid = ( l + r ) >> ;
build(lson(pos),l,mid) , build(rson(pos),mid+,r) , dat[pos] = dat[rson(pos)] * dat[lson(pos)];
}
inline void update(int pos,int l,int r,const int &tar) {
if( l == r ) return void(dat[pos]=trans[rec[l]]);
const int mid = ( l + r ) >> ;
tar <= mid ? update(lson(pos),l,mid,tar) : update(rson(pos),mid+,r,tar);
dat[pos] = dat[rson(pos)] * dat[lson(pos)];
}
inline Matrix query(int pos,int l,int r,const int &ll,const int &rr) {
if( ll <= l && r <= rr ) return dat[pos];
const int mid = ( l + r ) >> ;
if( rr <= mid ) return query(lson(pos),l,mid,ll,rr);
else if( ll > mid ) return query(rson(pos),mid+,r,ll,rr);
else return query(rson(pos),mid+,r,ll,rr) * query(lson(pos),l,mid,ll,rr);
}
}sgt; inline void addedge(int from,int to) {
static int cnt = ;
t[++cnt] = to , nxt[cnt] = s[from] , s[from] = cnt;
}
inline void pre(int pos) {
siz[pos] = ;
for(int at=s[pos];at;at=nxt[at]) if( t[at] != fa[pos] ) {
dep[t[at]] = dep[pos] + , fa[t[at]] = pos , pre(t[at]) , siz[pos] += siz[t[at]];
if( siz[t[at]] > siz[son[pos]] ) son[pos] = t[at];
}
}
inline void dfs(int pos) {
++iid , mxd[top[rec[id[pos]=iid]=pos]=pos==son[fa[pos]]?top[fa[pos]]:pos] = iid , h[pos] = f[pos] = inf;
if( son[pos] ) dfs(son[pos]) , f[pos] = f[son[pos]] , h[pos] = ; // pos isn't a leaf node .
for(int at=s[pos];at;at=nxt[at]) if( t[at] != fa[pos] && t[at] != son[pos] ) dfs(t[at]) , h[pos] += f[t[at]];
f[pos] = std::min( f[pos] + h[pos] , v[pos] );
} inline lli query(int pos) {
Matrix ret = sgt.query(,,n,id[pos],mxd[top[pos]]);
ret = ini * ret;
return ret[][];
} inline void update(int pos) {
while(pos) {
trans[pos][][] = v[pos] , trans[pos][][] = h[pos] ,
sgt.update(,,n,id[pos]) , pos = top[pos];
if( pos == ) break; // root don't have fa .
Matrix fs = ini * sgt.query(,,n,id[pos],mxd[pos]);
h[fa[pos]] -= f[pos] , h[fa[pos]] += ( f[pos] = fs[][] );
pos = fa[pos];
}
} inline char nxtchar() {
static const int BS = << ;
static char buf[BS],*st=buf+BS,*ed=st;
if( st == ed ) ed = buf + fread(st=buf,,BS,stdin);
return st == ed ? - : *st++;
}
inline char realchar() {
char ret;
while( !isalpha(ret=nxtchar()) );
return ret;
}
inline int getint() {
int ret = , ch;
while( !isdigit(ch=nxtchar()) );
do ret=ret*+ch-''; while( isdigit(ch=nxtchar()) );
return ret;
}
inline int getlli() {
lli ret = , ch;
while( !isdigit(ch=nxtchar()) );
do ret=ret*+ch-''; while( isdigit(ch=nxtchar()) );
return ret;
} int main() {
static int m;
n = getint() , ini[][] = ini[][] = ;
for(int i=;i<=n;i++) v[i] = getlli();
for(int i=,a,b;i<n;i++) a = getint() , b = getint() , addedge(a,b) , addedge(b,a);
pre() , dfs();
for(int i=;i<=n;i++) trans[i][][] = , trans[i][][] = v[i] , trans[i][][] = inf , trans[i][][] = h[i];
sgt.build(,,n);
m = getint();
for(int i=,o,p;i<=m;i++) {
o = realchar() , p = getint();
if( o == 'Q' ) printf("%lld\n",query(p));
else if( o == 'C' ) v[p] += getlli() , update(p);
}
return ;
}

雪の降るこの街にも 暖かい光が差し
雪花飘飞的这条街道上 温暖光芒从天而降
折れた羽を癒してる 傷ついた心の奧
受伤心灵的深处 被祈愿之羽逐渐治愈
足音が聞こえてくる 明日への扉叩いて
我听到了谁的脚步声 有人在敲打通往明日的门扉
目の前の道を進む 季節が巡る 時の中で
季节更迭 我在时光之流中踏上眼前的道路
巡る想い あなただけ 笑顏が今もまぶしい
流连的思念 只有你的笑颜直到现在还如此炫目