1. 程式人生 > 其它 >「SCOI2015」小凸玩密室

「SCOI2015」小凸玩密室

「SCOI2015」小凸玩密室

題目連結:#2009. 「SCOI2015」小凸玩密室 - 題目 - LibreOJ (loj.ac)


​ 由於題目中有這麼一個條件:在點燈的過程中,要保證任意時刻所有被點亮的燈泡必須連通,在點亮一個燈泡後必須先點亮其子樹所有燈泡才能點亮其他燈泡。請告訴他們,逃出密室的最少花費是多少。。所以我們容易想到樹形 dp ,然後對子樹進行合併。

​ 我們可以很容易設計這麼一個狀態:\(dp_{i,j,0/1}\) 表示點亮以 \(i\) 為根的子樹,最後一個被點亮的燈泡是 \(j\)\(0/1\) 表示起點是否是當前節點。

​ 那麼合併的轉移式就是:

\[\begin{aligned} dp_{i,j,1}&=min_{j=le}^{mid}(dp_{rs,k,1}+(dis_{k,i}+lw_i)*a_{ls[i]}+dp_{ls,j,1}) \\ dp_{i,j,1}&=min_{j=mid+1}^{ri}(dp_{ls,k,1}+(dis_{k,i}+rw_i)*a_{rs[i]}+dp_{rs,j,1}) \\ dp_{i,j,0}&=min_{j=le}^{mid}(dp_{rs,k,0}+rw_i*a_i+lw_i*a_{ls[i]}+dp_{ls,j,1}) \\ dp_{i,j,0}&=min_{j=mid+1}^{ri}(dp_{ls,k,0}+rw_i*a_{rs[i]}+lw_i*a_i+dp_{rs,j,1}) \end{aligned} \]

​ 分兩部分轉移,一條是先走右子樹再走左子樹,一條條是先走左子樹再走右子樹。

\(le,ri\) 是這個節點管轄的最左葉子和最右葉子,\(mid\) 則是這個節點的左兒子管轄的最右端點。這些可以通過一個 dfs 預處理出來 。

​ 這麼開空間是 \(O(n^2)\) 的。但是我們發現這個一顆完全二叉樹,而且每個節點訪問的範圍是 \(l_i,r_i\)。將同一層的節點所有的 \(l_i,r_i\) 合起來剛好是 \(n\) (應該是葉子個數) 個,而一共只有 \(\log n\) 層。所以我們對每層開一個空間為 \(n\) 的陣列,總空間複雜度就降到了 \(O(n\log n)\)

​ 然後我們來分析一下時間複雜度,對於每個節點,可以在 \(O(cnt)\) 的時間內預處理出倆子樹的最小 \(dp\)

值,然後 \(O(cnt)\) 進行轉移( cnt 表示該節點管轄的葉子數目)。所以時間跟空間一樣,複雜度為:\(O(n\log n)\)

​ 由於題目中所給的二叉樹不是滿二叉樹,所以還可能出現有一個節點只有左兒子而沒有右兒子的狀況,由於這樣的點最多存在一個,所以特判一下就好了。

程式碼如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 2e5+5;
const ll INF = 1e17; 
int n,ls[MAXN],rs[MAXN],idx[MAXN],pos[MAXN],Siz;
ll dp[22][MAXN][2],a[MAXN],lw[MAXN],rw[MAXN],tmp[22][MAXN][2],dis[22][MAXN],le[MAXN],ri[MAXN];
void pre_dfs(int p,int dep)
{
	if(!p) return ;
	for(int i=1;i<dep;++i)
		dis[i][p]=dis[i][p/2]+((p&1)?rw[p/2]:lw[p/2]);
	if(!ls[p]&&!rs[p])
	{
		idx[++Siz]=p;
		le[p]=ri[p]=Siz;
		return ;
	}
	if(ls[p]) pre_dfs(ls[p],dep+1);
	if(rs[p]) pre_dfs(rs[p],dep+1);
	le[p]=min(le[ls[p]],le[rs[p]]),ri[p]=max(ri[ls[p]],ri[rs[p]]);
	if(!rs[p]) le[p]=le[ls[p]],ri[p]=ri[ls[p]];
}
void dfs(int k,int dep)
{
	int l=le[k],r=ri[k],mid=ri[ls[k]];
	if(!ls[k]&&!rs[k])
	{
		dp[dep][l][1]=0;
		dp[dep][l][0]=0;
		return ;
	}
	if(ls[k]&&!rs[k])
	{
		dp[dep][l][1]=lw[k]*a[ls[k]];
		dp[dep][0][0]=lw[k]*a[k];
		dp[dep][l][0]=INF;
		dp[dep+1][l][0]=dp[dep+1][l][1]=0;
		return ;
	}
	dfs(ls[k],dep+1);dfs(rs[k],dep+1);
	ll val[2];val[0]=val[1]=INF;
	for(int i=l;i<=mid;++i)
	{
		val[1]=min(val[1],lw[k]*a[ls[k]]+dp[dep+1][i][1]+(dis[dep][idx[i]]+rw[k])*a[rs[k]]);
		val[0]=min(val[0],min(dp[dep+1][i][1],dp[dep+1][i][0])+dis[dep][idx[i]]*a[k]+rw[k]*a[rs[k]]);
	}
	if(!rs[ls[k]]) val[0]=min(val[0],dp[dep+1][0][0]+lw[k]*a[k]+rw[k]*a[rs[k]]);
	for(int i=mid+1;i<=r;++i)
	{
		dp[dep][i][1]=val[1]+dp[dep+1][i][1];
		dp[dep][i][0]=val[0]+dp[dep+1][i][1];
	}
	val[0]=val[1]=INF;
	for(int i=mid+1;i<=r;++i)
	{
		val[1]=min(val[1],rw[k]*a[rs[k]]+dp[dep+1][i][1]+(dis[dep][idx[i]]+lw[k])*a[ls[k]]);
		val[0]=min(val[0],min(dp[dep+1][i][1],dp[dep+1][i][0])+dis[dep][idx[i]]*a[k]+lw[k]*a[ls[k]]);
	}
	if(!rs[rs[k]]) val[0]=min(val[0],dp[dep+1][0][0]+rw[k]*a[k]+lw[k]*a[ls[k]]);
	for(int i=l;i<=mid;++i)
	{
		dp[dep][i][1]=val[1]+dp[dep+1][i][1];
		dp[dep][i][0]=val[0]+dp[dep+1][i][1];
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
	for(int i=2;i<=n;++i)
	{
		ll b;scanf("%lld",&b);
		if(i%2==1) rs[i/2]=i,rw[i/2]=b;
		else ls[i/2]=i,lw[i/2]=b;
	}
	pre_dfs(1,1);
	dfs(1,1);
	ll res=1e18;
	for(int i=1;i<=Siz;++i)
		for(int j=0;j<=1;++j)
			res=min(res,dp[1][i][j]);
	if(ls[1]&&!rs[1]) res=min(res,dp[1][0][0]);
	printf("%lld\n",res);
	return 0;
}
路漫漫其修遠兮,吾將上下而求索。