1. 程式人生 > 實用技巧 >[SCOI2015]小凸玩密室

[SCOI2015]小凸玩密室

題目

題目
當然,根固定為\(1\),但是第一個被點亮的燈不一定是\(1\)

做法

這裡我只會講最終做法,但是如果你要問這個結果到底是怎麼得到的,其中的心路歷程是什麼,這篇部落格:https://www.luogu.com.cn/blog/MachineryCountry/solution-p4253相信能給你不錯的體驗。

首先,我們先觀察葉子節點,一個葉子節點,跑完之後,要麼點亮其的一個祖先(會出現這種情況一般是因為第一個被點亮的燈不是\(1\)),要麼點亮其祖先的另外一個兒子,推廣發現每一個點在遍歷完其子樹後,都是這種情況。

又發現,完全二叉樹有一個極其優美的性質,樹的高度為\(logn\)層。

不妨考慮\(f_{i,j}\)

表示第\(i\)個子樹遍歷完之後點亮的是第\(j+1\)個祖先的最小花費,\(g_{i,j}\)則是第\(j+1\)個祖先的一個兒子的最小花費。

\(g_{i,j}=min(a_{lc}*b_{lc}+g_{lc,0}+g_{rc,j+1},a_{rc}*b_{rc}+g_{rc,0}+g_{lc,j+1})\)
\(f_{i,j}=min(a_{lc}*b_{lc}+g_{lc,0}+f_{rc,j+1},a_{rc}*b_{rc}+g_{rc,0}+f_{lc,j+1})\)

其餘的狀態方程就不列了,比較麻煩。

我還專門設定了\(t_{i}\)表示遍歷\(i\)的子樹的花費。

但是需要注意的是,不管是\(f,g,t\)

\(i\)號點被點亮的費用都是沒有算進去的,因為遍歷\(i\)的子樹,其子樹內的點\(x\)肯定都是從另外一個\(i\)子樹內的點\(y\)出發去亮\(x\)的,因此可以樹形\(DP\)直接計算,但是\(x\)不一樣,\(x\)是靈活應變的,只有外面知道他是被哪個點點亮的,而樹形DP基本上只能處理子樹內的點,所以只能不算點亮\(x\)的花費。(事實上,也正是因為樹形DP只能處理子樹內的點,所以從\(i\)子樹出來到達的點還必須多設定一維來處理)

程式碼

時間複雜度:\(O(nlogn)\).

其實我的程式碼打複雜了,如果想要看程式碼好看一些的可以去你谷的題解區看看,裡面的許多程式碼都寫的比我優美。

#include<cstdio>
#include<cstring>
#define  N  210000
#define  SN  20 //17
using  namespace  std;
typedef  long  long  LL;
template<class  T>
inline  T  mymin(T  x,T  y){return  x<y?x:y;}
template<class  T>
inline  T  mymax(T  x,T  y){return  x>y?x:y;}
int  fa[N][SN],other[N]/*另外一個兒子*/,n;
LL  f[N][SN],g[N][SN],dis[N],a[N],b[N],t[N];
void  dfs(int  x)
{
	for(int  i=1;i<=17;i++)
	{
		fa[x][i]=fa[fa[x][0]][i-1];
		if(!fa[x][i])break;
	}
	for(int  i=0;i<=1;i++)
	{
		int  y=(x<<1)+i;
		if(y>n)continue;
		dis[y]=dis[x]+b[y];fa[y][0]=x;
	}
	for(int  i=0;i<=1;i++)
	{
		int  y=(x<<1)+i;
		if(y>n)continue;
		other[x]=y^1;
		dfs(y);
	}
	for(int  i=0;i<=17;i++)
	{
		if(!fa[x][i])break;
		if((x<<1)>n) 
		{
			g[x][i]=(dis[other[fa[x][i]]]+dis[x]-2*dis[fa[x][i]])*a[other[fa[x][i]]];
			f[x][i]=(dis[x]-dis[fa[x][i]])*a[fa[x][i]];
		}
		else  if((x<<1)==n)//只有一個兒子
		{
			int  lc=x<<1;
			g[x][i]=b[lc]*a[lc]+g[lc][i+1];
			f[x][i]=b[lc]*a[lc]+f[lc][i+1];
		}
		else//兩個兒子都有 
		{
			int  lc=x<<1,rc=(x<<1)+1;
			g[x][i]=mymin(b[lc]*a[lc]+g[lc][0]+g[rc][i+1],b[rc]*a[rc]+g[rc][0]+g[lc][i+1]);
			f[x][i]=mymin(b[lc]*a[lc]+g[lc][0]+f[rc][i+1],b[rc]*a[rc]+g[rc][0]+f[lc][i+1]);
		}
	}
	if((x<<1)>n)t[x]=0;
	else  if((x<<1)==n)t[x]=t[x<<1]+b[x<<1]*a[x<<1];
	else
	{
		int  lc=x<<1,rc=(x<<1)+1;
		t[x]=mymin(b[lc]*a[lc]+g[lc][0]+t[rc],b[rc]*a[rc]+g[rc][0]+t[lc]);
	}
}
inline  int  pd_son(int  x,int  f){return  (f<<1)==x?0:1;}
LL  solve(int  x)//假設x為根,且x不是1號點 
{
	LL  ans=f[x][0];
	int  ff=(x>>1);
	while(ff>1)
	{
		int  y=x^1/*x的兄弟*/;
		if(y>n/*x是ff的唯一兒子*/)ans+=b[ff]*a[ff>>1];
		else  ans+=f[y][1]+a[y]*b[y];
		x=ff;ff=(x>>1);
	}
	int  y=x^1/*x的兄弟*/;
	if(y<=n/*x是ff的唯一兒子*/)ans+=t[y]+a[y]*b[y];
	return  ans; 
}
int  main()
{
	scanf("%d",&n);
	for(int  i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(int  i=2;i<=n;i++)scanf("%lld",&b[i]);
	dfs(1);
	LL  ans=t[1];
	for(int  i=2;i<=n;i++)
	{
		ans=mymin(ans,solve(i));
	}
	printf("%lld\n",ans); 
	return  0;
}