luogu P7201 [COCI2019-2020#1] Džumbus
题面传送门
域值这么大显然不能把域值放到dp里去。
但是\(n\)很小可以开两维。
所以可以设\(f_{i,j}\)为\(i\)子树内有\(j\)个点被选择的最小费用。
但是这样状态表示很不明确。
所以再增添一维\(0/1/2\)代表没选,选了而没有成为贡献,选了成为贡献。
然后平凡转移一下即可。注意树形dp常见的\(O(n^2)\)套路。
然后对于每个询问处理后缀\(\min\)二分即可。
时间复杂度\(O(n^2+qlogn)\)
code:
#include<cstdio>
#include<cstring>
#define N 1100
#define ll long long
#define I inline
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
int n,m,k,x,y,z,w[N+5],fa[N+5],fl[N+5],siz[N+5];ll dp[N+5][3][N+5],b[N+5],l,r,mid;
I int findfa(int x){return x==fa[x]?x:fa[x]=findfa(fa[x]);}
struct yyy{int to,z;};
struct ljb{
int head,h[N+5];yyy f[N+5<<2];
I void add(int x,int y){f[++head]=(yyy){y,h[x]},h[x]=head;}
}s;
I void dfs(int x,int last){
dp[x][1][0]=w[x];dp[x][0][0]=0;yyy tmp;int i,j,cur;siz[x]=1;ll now;
for(cur=s.h[x];cur;cur=tmp.z){
tmp=s.f[cur];if(tmp.to==last) continue;dfs(tmp.to,x);
for(i=siz[x];~i;i--){
for(j=siz[tmp.to];~j;j--){
now=min(dp[tmp.to][0][j],min(dp[tmp.to][1][j],dp[tmp.to][2][j]));dp[x][0][i+j]=min(dp[x][0][i+j],dp[x][0][i]+now);
//if(!x&&j==2&&i==2) printf("%d %lld %lld %lld\n",tmp.to,dp[x][0][i],dp[tmp.to][2][j],dp[x][0][i+j]);
dp[x][1][i+j]=min(dp[x][1][i+j],dp[x][1][i]+dp[tmp.to][0][j]);
now=min(dp[x][1][i]+dp[tmp.to][2][j],dp[x][2][i]+dp[tmp.to][1][j]);dp[x][2][i+j+1]=min(dp[x][2][i+j+1],now);
dp[x][2][i+j+2]=min(dp[x][2][i+j+2],dp[x][1][i]+dp[tmp.to][1][j]);
now=dp[x][2][i]+min(dp[tmp.to][2][j],dp[tmp.to][0][j]);dp[x][2][i+j]=min(dp[x][2][i+j],now);
}
}
siz[x]+=siz[tmp.to];
}
}
int main(){
register int i,j;scanf("%d%d",&n,&m);for(i=1;i<=n;i++) fa[i]=i,scanf("%d",&w[i]);memset(dp,0x3f,sizeof(dp));w[0]=2e9;
for(i=1;i<=m;i++)scanf("%d%d",&x,&y),s.add(x,y),s.add(y,x),fa[findfa(x)]=findfa(y);
for(i=1;i<=n;i++)fl[findfa(i)]=1;for(i=1;i<=n;i++) fl[i]&&(s.add(0,i),s.add(i,0),0);dfs(0,0);
for(b[n+1]=2e18,i=n;i;i--) b[i]=min(b[i+1],dp[0][0][i]);
scanf("%d",&k);while(k--){
scanf("%d",&x);l=1;r=n+1;
while(l+1<r) mid=l+r>>1,(b[mid]<=x?l:r)=mid;printf("%d\n",l==1?0:l);
}
}