@ysner
2018-07-19T14:57:07.000000Z
字数 3185
阅读 1776
DP
二分
给一棵有个节点的带点权树,有次询问,问如果要求一条路径上点权和小于等于,那么这条路径最多能包括多少点?
从每一个点出发,看最多能走多远。
注意存在的情况!!!
复杂度上限
直接暴力。
设表示自点向下,点权和为时最多能向下走多远。
设表示以点为根的子树中,点权和为的路径最多有多长。
转移方程
il void dfs(re int u,re int fa)
{
re int flag=0;
for(re int i=h[u];i+1;i=e[i].nxt)
{
re int v=e[i].to;
if(v==fa) continue;flag=1;
dfs(v,u);
fp(i,0,20) fp(j,0,i) f[u][i]=max(f[u][i],g[u][j]+g[v][i-j]);
fp(i,0,20-w[u]) g[u][i+w[u]]=max(g[u][i+w[u]],g[v][i]+1);
}
if(!flag) if(w[u]<=20) g[u][w[u]]=1;
}
il void dfss(re int u,re int fa,re ll s,re int dis)
{
if(s<=lim) ans1=max(ans1,dis);
for(re int i=h[u];i+1;i=e[i].nxt)
{
re int v=e[i].to;
if(v==fa) continue;
if(s+w[v]<=lim) dfss(v,u,s+w[v],dis+1);
}
}
int main()
{
memset(h,-1,sizeof(h));
n=gi();Q=gi();
fp(i,1,n) w[i]=gi();
fp(i,1,n-1)
{
re int u=gi(),v=gi();
add(u,v);add(v,u);
}
if(n<=100&&Q<=4000)
{
fp(i,1,Q)
{
ans1=0;lim=gi();
fp(i,1,n) dfss(i,i,w[i],1);
printf("%lld\n",ans1);
}
return 0;
}
dfs(1,0);
fp(i,0,20) fp(j,1,n) ans[i]=max(ans[i],f[j][i]);
fp(i,1,20) ans[i]=max(ans[i],ans[i-1]);
while(Q--)
{
printf("%lld\n",ans[gi()]);
}
return 0;
}
随机数据等价于
有一个套路:
如果一个数组,状态范围大而结果范围小,可以将结果与状态交换位置。
于是我们可以把表示的状态改为以点为根的子树中,包含点数为的路径需要的最小点权和。同理。
转移差不多。并且由于数组不需要上传,这一维实际上可以省掉。
这样就可以处理很大的情况了。
当然,询问时,由于链长越大需要的点权和也越大,符合单调性,可以二分答案。
复杂度左右。
树高很大。
复杂度可以达到,会的。
然而每个点枚举链长时,不一定要枚到。不是每个点下面都有个点。
于是只要枚到该点离自己子树下叶节点最远距离即可。
复杂度?玄学。。。反正能过。
注意一下二分时,判断条件应该是合法或不合法,不能两者混杂。
比如说不能写成
while(l<=r)
{
re ll mid=l+r>>1;
if(f[mid]>=lim) ans=mid,r=mid-1;
else l=mid+1;
}
还有鬼知道为什么数据会有答案大于的。。。
其实是出题人认为树根深度为。。。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define ls x<<1
#define rs x<<1|1
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=2e4+100;
struct Edge{int to,nxt;}e[N<<1];
ll w[N],n,h[N],cnt,Q,lim,ans;
ll f[2005],g[N][1005],d[N];
il void add(re int u,re int v){e[++cnt]=(Edge){v,h[u]};h[u]=cnt;}
il ll gi()
{
re ll x=0,t=1;
re char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
il void dfs(re int u,re int fa)
{
d[u]=1;g[u][0]=0;g[u][1]=w[u];
for(re int i=h[u];i+1;i=e[i].nxt)
{
re int v=e[i].to;
if(v==fa) continue;
dfs(v,u);
fp(i,0,d[u]) fp(j,0,d[v]) f[i+j]=min(f[i+j],g[u][i]+g[v][j]);
d[u]=max(d[u],d[v]+1);
fp(i,1,d[u]) g[u][i]=min(g[u][i],g[v][i-1]+w[u]);
}
}
int main()
{
freopen("erusaert.in","r",stdin);
freopen("erusaert.out","w",stdout);
memset(h,-1,sizeof(h));memset(f,63,sizeof(f));memset(g,63,sizeof(g));
n=gi();Q=gi();
fp(i,1,n) w[i]=gi();
fp(i,1,n-1)
{
re int u=gi(),v=gi();
add(u,v);add(v,u);
}
dfs(1,0);
while(Q--)
{
lim=gi();
re ll l=0,r=2000,ans=0;
while(l<=r)
{
re ll mid=l+r>>1;
if(f[mid]<=lim) ans=mid,l=mid+1;
else r=mid-1;
}
printf("%lld\n",ans);
}
fclose(stdin);
fclose(stdout);
return 0;
}