LUOGU P4253 [SCOI2015]小凸玩密室(树形dp)

传送门

解题思路

玄学树形\(dp\),题目描述极其混乱。。。看错了两次题,设首先根据每次必须点完子树里的灯才能点别的,那么点灯情况只有两种,第一种是点到某一个祖先,第二种是点到某一个祖先的兄弟。所以可以设出状态\(f[i][j][0/1]\)表示以\(i\)为根的子树已经点完,\(0\)表示下一步点到\(j\)祖先,\(1\)表示下一步点到\(j\)祖先的兄弟。然后转移的时候讨论一下这个点有几个儿子。最后算答案时枚举起点,然后一步一步往上跳,有兄弟的跳兄弟。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define int long long

using namespace std;
const int MAXN =200005;

inline int rd(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {f=ch=='-'?0:1;ch=getchar();}
	while(isdigit(ch))  {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	return f?x:-x;
}

int n,a[MAXN],f[MAXN][30][2],dis[MAXN][30],ans=1e18,ansN;

inline int p(int i,int j){
	return ((1<<(j-1))<=i)?(i>>j):-1;
}

inline int b(int i,int j){
	return ((i>>(j-1))^1);
}

signed main(){
	n=rd();dis[1][1]=0;
	for(int i=1;i<=n;i++) a[i]=rd();
	for(int i=2;i<=n;i++){
		dis[i][1]=rd();
		for(int j=2;~p(i,j);j++)
			dis[i][j]=dis[p(i,1)][j-1]+dis[i][1];
	}
	for(int i=n;i;i--)
		for(int j=1;~p(i,j);j++){
		if((i<<1)>n) {
			f[i][j][0]=dis[i][j]*a[p(i,j)];
			f[i][j][1]=(dis[i][j]+dis[b(i,j)][1])*a[b(i,j)];
			
		}
		else if((i<<1|1)>n){
			f[i][j][0]=f[i<<1][j+1][0]+dis[i<<1][1]*a[i<<1];
			f[i][j][1]=f[i<<1][j+1][1]+dis[i<<1][1]*a[i<<1];
		}
		else {
			f[i][j][0]=min(f[i<<1][j+1][0]+f[i<<1|1][1][1]+dis[i<<1|1][1]*a[i<<1|1],
						   f[i<<1|1][j+1][0]+f[i<<1][1][1]+dis[i<<1][1]*a[i<<1]);
			f[i][j][1]=min(f[i<<1][j+1][1]+f[i<<1|1][1][1]+dis[i<<1|1][1]*a[i<<1|1],
						   f[i<<1|1][j+1][1]+f[i<<1][1][1]+dis[i<<1][1]*a[i<<1]);
		}
	}
	for(int i=1;i<=n;i++){
		ansN=f[i][1][0];
		for(int j=p(i,1),last=i;~j;j=p(j,1),last=p(last,1)){
			if(b(last,1)<=n)
				ansN+=dis[b(last,1)][1]*a[b(last,1)]+f[b(last,1)][2][0];
			else 
				ansN+=dis[j][1]*a[p(j,1)];	 
		}
		ans=min(ans,ansN);
	}cout<<ans;
	return 0;
}
posted @ 2018-10-17 17:29  Monster_Qi  阅读(160)  评论(0编辑  收藏  举报