該來的還是來了,我是真的不想寫這個狗博客,是真的煩。
參考文獻
很詳細:https://www.luogu.com.cn/blog/ShadowassIIXVIIIIV/solution-p4643
ZFY:抄襲文獻
術語
,的輕兒子。,的重兒子。
例題
例題
(#`O′)喂,普通都沒講,爲什麼直接加強了!!!
思路
這道題目我們一看特別容易矇蔽,修改,但是當我們想到鏈分治後,一大坨的奇思妙想就來了。
看看普通的DP方程式。
設爲不在獨立集中的最大值,那麼就是在的最大值,爲點權值。
那麼就有DP方程式:
而這個搜索的順序是無關緊要的,所以我們可以優先重鏈,然後輕鏈。
也就是先DFS重鏈並且記錄所有輕兒子,然後再按順序DFS輕兒子。
所以我們可以把樹抽象成這樣:
然後我們對於每個點,我們記錄一個值:
然後我們就可以單獨對於重鏈跑DP了,成功把樹上轉換成鏈上,DP也就是:
然後我們就可以用鏈分治(樹鏈剖分)了,然後用線段樹。
等會等會,怎麼用線段樹?
考慮一些的本質,有一些DP其實就是一個數組乘上一個矩陣然後變成了DP後的數組,只不過有時候有優化罷了。
這個也是一樣,不過矩陣乘法的形式跟以前不一樣,是:
可以證明,這個矩陣乘法的形式依舊滿足結合律,這就夠了,單位矩陣爲:到全是,其他全是。
那麼我們把矩陣寫出來!!!
爲了方便,下文的
用樹鏈剖分維護的話,現在就是(線段樹維護後面的那個矩陣)。
全局平衡二叉樹
以下摘自參考文獻。
可以去翻07年的論文”QTREE 解法的一些研究",(“全局平衡二叉樹"這個中二的名字是論文裏說的)
衆所周知把剛纔的樹剖換成lct就可以做到一個log了,但是我們發現lct實在是常數太!大!了!絕對是跑不過實現的優秀的一點的樹剖的
但是我們對於lct的複雜度證明卻很感興趣,爲啥同樣是操作了個數據結構,把線段樹換成常數更大的splay複雜度反而少了一個呢?(剛纔這句話嚴格來講是病句,常數和複雜度沒有任何關聯)
具體證明需要用到勢能分析,但是感性理解一下就是如果我們把lct上的虛邊也看成splay的邊的話,我們發現整棵lct變成了一棵大splay,只是有些點度數不是2了
但是這些點度不是2的點並未破壞splay的勢能分析換句話說勢能分析對整顆大splay仍然生效,所以你的次splay在整個大splay上只是一次splay而已
複雜度自然是均攤了
但是,我們發現這是顆靜態樹,使用splay實在是大(常)材(數)小(過)用(大)了
於是我們考慮將lct強行靜態化,換句話說,建一個像splay一樣的全局平衡的樹
觀察到線段樹只是局部平衡的,在碰到專業卡鏈剖的數據–鏈式堆(根號n個長度爲根號n的鏈連成完全二叉樹的形狀)的時候會導致算上虛邊之後的整顆樹左傾或者右傾
此時我們發現如果在建線段樹的時候做點手腳,我們把線段樹換成二叉查找樹bst,並且這個bst不是嚴格平衡的話,我們可以做到更加優秀的複雜度,使得算上虛邊之後的樹樹高達到級別
我們還是在樹上dfs,但是對於重鏈建bst的時候我們並不建一個完美的bst,而是將每一個節點附上一個權值,權值爲它所有輕兒子的siz之和+1,然後我們每次找這個鏈的帶權重心,把他作爲這一級的父親,然後遞歸兩邊進行建bst
當然我們發現最壞情況下我們可以建出一個嚴重左傾或者右傾的bst
但是,我們考慮算上虛邊的整顆樹我們會發現一個神奇的性質,無論是經過一條重的二叉樹邊還是虛邊,所在子樹的siz至少翻一倍,而這個性質在原來的線段樹上是沒有的
所以這個大bst的高度是的
當然,這個bst既不能旋轉也不能splay,所以維護區間信息會比較吃力,但是,我們爲什麼要維護區間信息呢?這是動態dp啊,我們只需要維護這整個重鏈的矩陣連乘積就行了……,所以維護整個重鏈的連乘積還是可以做到的
另外這個東西看起來聽玄學其實比樹剖還好寫……
這裏對於對於全局平衡二叉樹的時間複雜度進行證明。
在全局平衡二叉樹上,每跳一個虛邊,那麼在原樹上對應的就是子數大小乘2(最壞情況),同時每條一條實邊,也就是重邊(全局平衡二叉樹上),由於我們每次的根都是帶權重心,左右兒子相對平衡,所以也是子數大小乘2(最壞情況),那麼就是層了。
關於構造方法其實shadowice1984講的特別好,Orz,奆佬還是奆佬,我還是蒟蒻QAQ。
代碼與細節
上代碼!
#include<cstdio>
#include<cstring>
#define inf 999999999
#define N 1100000
#define M 2100000
using namespace std;
template <class T>
inline void getz(register T &x)
{
x=0;
register T f=1;register char c=getchar();
while(c<'0' || c>'9')c=='-'?f=-1:0,c=getchar();
while(c<='9' && c>='0')x=(x<<3)+(x<<1)+(c^48),c=getchar();
x*=f;
}
template <class T>
inline T mymin(register T x,register T y){return x<y?x:y;}//最小值
template <class T>
inline T mymax(register T x,register T y){return x>y?x:y;}//最大值
int zhi[N],n,m;//點值
//---------------------------------------------------------
struct bian
{
int y,next;
}a[M];int last[N],len;
inline void ins(int x,int y){len++;a[len].y=y;a[len].next=last[x];last[x]=len;}
//邊目錄
int h[N],size[N],lsiz[N];
inline void dfs(int x,int fa)
{
size[x]=1;
for(int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if(y!=fa)
{
dfs(y,x);
size[x]+=size[y];if(size[y]>size[h[x]])h[x]=y;//改變重兒子
}
}
lsiz[x]=size[x]-size[h[x]];//表示的是在BST上他的權值
}
//建立重心以及size
struct node
{
int mp[2][2];
inline node(){mp[0][0]=mp[0][1]=mp[1][0]=mp[1][1]=-inf;}
inline int* operator [](int x){return mp[x];}
};
inline node operator*(node x,node y)//重定義乘法
{
node ans;
ans[0][0]=mymax(x[0][0]+y[0][0],x[0][1]+y[1][0]);
ans[0][1]=mymax(x[0][0]+y[0][1],x[0][1]+y[1][1]);
ans[1][0]=mymax(x[1][0]+y[0][0],x[1][1]+y[1][0]);
ans[1][1]=mymax(x[1][0]+y[0][1],x[1][1]+y[1][1]);
return ans;
}
inline void mer(node &x){x[0][0]=x[1][1]=0;x[0][1]=x[1][0]=-inf;}//等於單位矩陣
inline int gtw(node x){return mymax(mymax(x[0][0],x[0][1]),mymax(x[1][0],x[1][1]));}
//矩陣
struct Tree
{
node mul[N]/*表示的是每個點在BST上管理的權值*/,w[N]/*每個點本身的權值*/;
int son[N][2],fa[N],root;
//一下是基礎函數
void pre_do()//初始化
{
mer(mul[0]);mer(w[0]);//初始化
for(int i=1;i<=n;i++)w[i][0][0]=w[i][1][0]=0,w[i][0][1]=zhi[i];
}
void ud(register int x){mul[x]=mul[son[x][0]]*w[x]*mul[son[x][1]];}//表示的是維護自己管理的值
void giv(register int x,register int v){w[x][1][0]=w[x][0][0]+=gtw(mul[v]);w[x][0][1]+=mymax(mul[v][0][0],mul[v][1][0]);fa[v]=x;}//表示v是x的輕兒子,讓v認x父親
bool pd(register int f,register int x){return !(son[f][0]!=x && son[f][1]!=x);}//判斷x是不是f的重兒子
//以下是建樹
int sta[N],top;//表示的是重鏈區間
bool v[N];//是否被訪問
int bt(int l,int r)//返回根
{
if(l>r)return 0;
int sum=0;
for(register int i=l;i<=r;i++)sum+=lsiz[sta[i]];
int now=lsiz[sta[l]];
for(register int i=l;i<=r;i++,now+=lsiz[sta[i]])
{
if(now*2>=sum)
{
int rs=bt(l,i-1),ls=bt(i+1,r);fa[ls]=fa[rs]=sta[i];
son[sta[i]][0]=ls;son[sta[i]][1]=rs;ud(sta[i]);
return sta[i];
}
}
}
int sbuild(int x)
{
for(register int i=x;i;i=h[i])v[i]=1;
for(register int i=x;i;i=h[i])
{
for(register int k=last[i];k;k=a[k].next)
{
int y=a[k].y;
if(!v[y])giv(i,sbuild(y));
}
}
top=0;for(register int i=x;i;i=h[i])sta[++top]=i;
return bt(1,top);
}
void modify(int x,int W)//表示修改權值
{
w[x][0][1]+=W-zhi[x];zhi[x]=W;//表示修改權值
for(register int i=x;i;i=fa[i])
{
if(!pd(fa[i],i) && fa[i])
{
w[fa[i]][0][0]-=gtw(mul[i]);w[fa[i]][1][0]=w[fa[i]][0][0];w[fa[i]][0][1]-=mymax(mul[i][0][0],mul[i][1][0]);
ud(i);
w[fa[i]][0][0]+=gtw(mul[i]);w[fa[i]][1][0]=w[fa[i]][0][0];w[fa[i]][0][1]+=mymax(mul[i][0][0],mul[i][1][0]);
}
else ud(i);
}
}
}bst;
//全局平衡二叉樹
int main()
{
getz(n);getz(m);
for(register int i=1;i<=n;i++)getz(zhi[i]);
for(register int i=1;i<n;i++)
{
int x,y;getz(x);getz(y);
ins(x,y);ins(y,x);
}
dfs(1,0);
bst.pre_do();
bst.root=bst.sbuild(1);//初始化
register int lastans=0;
for(register int i=1;i<=m;i++)
{
int x,W;getz(x);getz(W);x^=lastans;
bst.modify(x,W);
printf("%d\n",lastans=gtw(bst.mul[bst.root]));
}
return 0;
}
問題1
在處理一條重鏈的時候,整個鏈的連乘我們知道,但是怎麼知道單單對於這個重鏈以及他們輕兒子的答案?
這個問題很簡單,就是直接提取出來,可以認爲乘了一個矩陣:,也就是這一行。
inline int gtw(node x){return mymax(mymax(x[0][0],x[0][1]),mymax(x[1][0],x[1][1]));}
問題2
在處理的時候,如何從兒子節點的矩陣中找到一點有用的信息?
例如知道一個節點的其中一個輕兒子在BST中的矩陣,但是如何從矩陣中翻出一點有用的東西???
也可以認爲就是乘了一個矩陣然後的得到了輕兒子的。