#树形dp,二分#UVA1407 Caves 洛谷 3698 [CQOI2017]小Q的棋盘

题目

给定一棵树,现在需要找到一条由节点1出发长度为\(x\)的路径,
问最多经过的节点数,重复经过只计算一次(不一定是简单路径)
UVA的那道题多组数据多组询问,边权还不一定是1,\(n\leq 500\)


分析

\(dp[x][ans][0/1]\)表示在以\(x\)为根的子树内,
经过的节点数为\(ans\),当前答案的终点是否为点\(x\)的最短路径长度
那么

\[dp[x][j+k][0]=\min\{dp[x][j][1]+dp[y][k][0]+w,dp[x][j][0]+dp[y][k][1]+w*2\} \]

\[dp[x][j+k][1]=\min\{dp[x][j][1]+dp[y][k][1]+w*2\} \]

由于\(\min\{dp[x][ans][0],dp[x][ans][1]\}\)显然具有单调性,所以可以二分
时间复杂度\(O(T(n^2+Qlog_2n))\)


代码

#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
const int N=511; struct node{int y,w,next;}e[N];
int dp[N][N][2],siz[N],as[N],ans[N],n,Test;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline void print(int ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
inline signed min(int a,int b){return a<b?a:b;}
inline void dfs(int x){
	dp[x][1][0]=dp[x][1][1]=0,siz[x]=1;
	for (rr int i=as[x];i;i=e[i].next){
		dfs(e[i].y),siz[x]+=siz[e[i].y];
		for (rr int j=siz[x]-siz[e[i].y];j;--j)
		for (rr int o=siz[e[i].y];o;--o){
			dp[x][j+o][0]=min(dp[x][j+o][0],dp[x][j][1]+dp[e[i].y][o][0]+e[i].w);
			dp[x][j+o][0]=min(dp[x][j+o][0],dp[x][j][0]+dp[e[i].y][o][1]+e[i].w*2);
			dp[x][j+o][1]=min(dp[x][j+o][1],dp[x][j][1]+dp[e[i].y][o][1]+e[i].w*2);
		}
	}
}
signed main(){
	while (1){
		n=iut(); if (!n) return 0;
		printf("Case %d:\n",++Test);
		memset(as,0,sizeof(as)),
		memset(dp,42,sizeof(dp));
		for (rr int i=1;i<n;++i){
			rr int x=iut()+1,F=iut()+1,w=iut();
			e[i]=(node){x,w,as[F]},as[F]=i;
		}
		dfs(1);
		for (rr int i=1;i<=n;++i)
		    ans[i]=min(dp[1][i][0],dp[1][i][1]);
		for (rr int Q=iut();Q;--Q){
			rr int W=iut();
		    rr int l=1,r=n;
		    while (l<r){
		    	rr int mid=(l+r+1)>>1;
		    	if (ans[mid]>W) r=mid-1;
		    	    else l=mid;
			}
			print(l),putchar(10);
		}
	}
} 
posted @ 2020-12-19 08:12  lemondinosaur  阅读(80)  评论(0编辑  收藏  举报