DreamOJ D10009
標籤
DP
線段樹
均攤
題意
給定一個包含 \(n\) 個節點根為 \(1\) 的樹,和一個序列 \(s\) 。
如果滿足以下兩個條件,那麼一個包含 \(k\) 條簡單路徑的可重集合認為是合法的。
- 這個可重集合中所有的路徑都是從根節點 \(1\) 出發。
- 令 \(c_i\) 為覆蓋節點 \(i\) 的路徑數量,如果對於每對擁有相同父親節點的節點 \((u,v)\) ,要求 \(|c_u-c_v| \leq 1\) 。
對於這個可重集合,其權值為 \(\sum_{i=1}^n c_is_i\) 。
可以證明,每組資料至少有一個可用的路徑集合,請你找到所有合法的可重集合中的最大權值。
題解
正解1
首先,由於 \(s[i]\ge0\) 所以,可重集中的路徑一定是越長越好的。我們不難證明,可重集中的每一條路徑都是一條由1號節點到某個葉子節點的路徑。
故一個相對暴力的做法就產生了:
我們對於每個節點動態開一個線段樹,維護其所有兒子到其子樹內葉子節點的最大權值。
接著用最大權值加上該節點本身的權值,轉遞給父節點建樹。
對於每一次從根節點找路徑的過程,我們從找到的葉子節點向上更新,把當前節點的貢獻在父節點線段樹中的位置賦為一個極小值,再遞迴父節點。這是為了在保證 \(|c_u-c_v| \leq 1\) 的情況下,每次找路徑選擇的都是被選擇次數最少的子節點的貢獻。當我們發現某個節點的線段樹找出來的最大值是極小值時,這就意味著它的所有子節點的貢獻都已經被選了一次,所以我們暴力重構該節點的線段樹。
這樣的複雜度直接與 \(k\) 掛鉤,由於 \(k\) 很大,複雜度顯然爆炸。考慮縮小 \(k\) 範圍,由於每次找路徑都是在子節點中輪流找路徑,故不是每次找路徑都要浪費線段樹來暴力找。初始時,我們認為需要從1號節點找 \(k\) 條路徑。我們遍歷整棵樹,把 \(k\div son\) 的次數交給子節點去找,視為一個子問題,該節點僅查詢 \(k\%son\) 條路徑(\(son\) 表示節點的兒子數量)。由於葉子節點無需繼續查詢,每個非葉子節點最多查兒子個數次路徑,總次數與 \(n\) 同階。然而重構操作仍然需要,比較難寫。複雜度可以近似為 \(O(logn\times \sum_{i=1}^n son[i]\times d[i])\)(\(son[i]\) 表示兒子數量,\(d[i]\) 為節點深度)。根據yd所說,由於 \(son[i]\) 與 \(d[i]\) 成反比,總複雜度似乎可以均攤成正確的 \(O(nlogn)\)。
程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
int T,n,k,a[N],b[N],pl[N],fa[N],ans,d[N];
vector<int>edge[N];
struct TREE{
int mx,wch;
};
struct node{
int siz;
vector<int>us1,us2;
int lson(int l,int r)
{
return (l+r)/2;
}
int rson(int l,int r)
{
return (l+r)/2+1;
}
TREE update(TREE x,TREE y)
{
if(x.mx<y.mx)
return y;
else
return x;
}
void pushup(int x)
{
if(tree[x*2].mx>tree[x*2+1].mx)
tree[x]=tree[x*2];
else
tree[x]=tree[x*2+1];
return;
}
void build(int l,int r,int x)
{
if(l>r)
return;
if(l==r)
{
tree[x]=TREE{us1[l],us2[l]};
return;
}
build(l,lson(l,r),x*2);
build(rson(l,r),r,x*2+1);
pushup(x);
return;
}
void change(int l,int r,int x,int to,TREE w)
{
if(l==r)
{
tree[x]=w;
return;
}
if(lson(l,r)>=to)
change(l,lson(l,r),x*2,to,w);
else
change(rson(l,r),r,x*2+1,to,w);
pushup(x);
return;
}
vector<TREE>tree;
}t[N];
void dfs(int x)
{
if(edge[x].size()==0)
return;
int w=b[x]/edge[x].size();
b[x]%=edge[x].size();
t[x].siz=edge[x].size();
t[x].tree.resize(edge[x].size()*4+10);
vector<int>us1(edge[x].size()+10);
vector<int>us2(edge[x].size()+10);
for(int i=0;i<edge[x].size();i++)
{
int to=edge[x][i];
d[to]=d[x]+1;
b[to]+=w;
dfs(to);
pl[to]=i+1;
if(edge[to].size()==0)
{
us1[i+1]=a[to];
us2[i+1]=to;
}
else
{
TREE xx=t[to].tree[1];
us1[i+1]=a[to]+xx.mx;
us2[i+1]=xx.wch;
}
}
t[x].us1=us1;
t[x].us2=us2;
t[x].build(1,t[x].siz,1);
return;
}
void check(int x)
{
vector<int>us1(edge[x].size()+10);
vector<int>us2(edge[x].size()+10);
if(t[x].tree[1].mx==INT_MIN)
{
for(int i=0;i<edge[x].size();i++)
{
int to=edge[x][i];
if(edge[to].size()==0)
{
us1[i+1]=a[to];
us2[i+1]=to;
}
else
{
TREE xx=t[to].tree[1];
us1[i+1]=a[to]+xx.mx;
us2[i+1]=xx.wch;
}
}
t[x].us1=us1;
t[x].us2=us2;
t[x].build(1,t[x].siz,1);
}
return;
}
void getnw(int st,int ed)
{
if(d[fa[st]]>=d[ed])
t[fa[st]].change(1,t[fa[st]].siz,1,pl[st],TREE{INT_MIN,0});
else
t[fa[st]].change(1,t[fa[st]].siz,1,pl[st],TREE{t[st].tree[1].mx+a[st],t[st].tree[1].wch});
check(fa[st]);
if(fa[st]!=1)
getnw(fa[st],ed);
return;
}
void dfs2(int x)
{
for(int i=0;i<edge[x].size();i++)
{
int to=edge[x][i];
dfs2(to);
}
if(edge[x].size()!=0)
{
for(int i=1;i<=b[x];i++)
{
TREE us=t[x].tree[1];
b[us.wch]++;
getnw(us.wch,x);
}
b[x]=0;
}
return;
}
void dfs3(int x)
{
for(int i=0;i<edge[x].size();i++)
{
int to=edge[x][i];
dfs3(to);
b[x]+=b[to];
}
ans+=b[x]*a[x];
return;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--)
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
b[i]=0;
edge[i].clear();
}
for(int i=2;i<=n;i++)
{
cin>>fa[i];
edge[fa[i]].push_back(i);
}
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
b[1]=k;
dfs(1);
dfs2(1);
ans=0;
dfs3(1);
cout<<ans<<'\n';
}
return 0;
}
可能的出錯點
出錯點挺多,如果要寫建議看看我的程式碼。
1.要開long long。
2.注意全域性變數與區域性變數。
3.注意update部分的細節。