DreamOJ D10009

SunLight_HHX發表於2024-04-02

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部分的細節。

正解2