BZOJ 3090: Coci2009 [podjela]

3090: Coci2009 [podjela]

Time Limit: 3 Sec  Memory Limit: 128 MB
Submit: 23  Solved: 17
[Submit][Status][Discuss]

Description

有 N 个农民, 他们住在 N 个不同的村子里. 这 N 个村子形成一棵树.
每个农民初始时获得 X 的钱.
每一次操作, 一个农民可以从它自己的钱中, 取出任意数量的钱, 交给某个相邻村子的农民.

对于每个农民给定一个值 v_i, 求
    (1) 最少需要多少次操作, 使得每个农民最终拿到的钱 >= 给定的值.
   

 

Input

    第1行: 一个整数 N (1 <= N <= 2000)
    第2行: 一个整数 X (0 <= X <= 10000)
    第3行: N 个整数, 表示 v_i. 保证所有 v_i 的和 <= N * X
    第4..N+2行: 每行两个 1..N 的数, 表示树上的一条边. 边是双向的.

 

Output

    第1行: 一个整数, 最少需要的操作次数

Sample Input

6
15
10 20 18 16 6 16
1 4
4 5
4 6
6 2
5 3

Sample Output

5

HINT

Source

想法:先想到一个普通Dp方程:$F[x][y]$表示以x为根的子树均合法下金钱数为y累到x上的最小移动次数,当成树背包转移就好了。但时空均不好看。

然后考虑到这里的答案上限为n-1,因为树没有环,所以村民可以等钱到了一起移动。

于是设Dp方程$F[x][y]$表示以x为根的子树均合法下,移动y次的累到x最多金钱数。

转移:y为x的一个儿子。

  1. $f[y][j]>=0$。可以不用移动,所以$F[x][i]->F'[x][i+j]$
  2. $F[x][i]+F[y][j]->F'[x][i+j+1]$

最后找最小的i 使得$F[root][i]>=0$就好了。

复杂度$O(n^2)$

 

#include< cstdio >
#include< cstring >

#define gec getchar
#define FILE(F) freopen(F".in","r",stdin),freopen(F".out","w",stdout)
#define DEBUG fprintf(stderr,"Passing [%s] in Line (%d)\n",__FUNCTION__,__LINE__);

typedef long long ll;
template
inline void read(T&x)
{
	x=0;bool f=0;char c=gec();
	for(;c<'0'||c>'9';c=gec())f=(c=='-');
	for(;c>='0'&&c<='9';c=gec())x=x*10+c-'0';
	x=f?-x:x;
}
const int MAXN(2010);
int F[MAXN][MAXN],G[MAXN],v[MAXN],n,X,a,b;
struct Node{int nd,nx;}bot[MAXN<<1];
int tot,first[MAXN],Siz[MAXN],cnt;
void add(int a,int b)
{bot[++tot]=(Node){b,first[a]};first[a]=tot;}
int max(int a,int b){return a>b?a:b;}
void DP(int x,int fa)
{
	for(int i=0;i<=n;i++)F[x][i]=X-v[x];Siz[x]=1;
	for(int y,v=first[x];v;v=bot[v].nx)
	if((y=bot[v].nd)!=fa)
	{	
		DP(y,x); Siz[x]+=Siz[y];
		memset(G,244,sizeof(int)*(Siz[x]+2));
		for(int i=0;i<=Siz[x]-Siz[y];i++)
		for(int j=0;j<=Siz[y];j++)
		{
			if(F[y][j]>=0) G[i+j]=max(G[i+j],F[x][i]);
			G[i+j+1]=max(G[i+j+1],F[x][i]+F[y][j]);
		}//看上去O(n^3),实际总共为O(n^2)
		for(int i=0;i<=Siz[x]+1;i++)F[x][i]=G[i];
	}
}
int main()
{
#ifndef ONLINE_JUDGE
	FILE("C");
#endif		
	read(n);read(X);
	for(int i=1;i<=n;i++)read(v[i]);
	for(int i=1;i<n;i++)
	{
		read(a);read(b);
		add(a,b);add(b,a);
	}
	DP(1,0);
	int i;for(i=0;F[1][i]<0;i++);
	printf("%d\n",i);
	return 0;
}
posted @ 2017-06-15 22:02  Oncle_Ha  阅读(266)  评论(0编辑  收藏  举报