#include <bits/stdc++.h>
using namespace std;
#define int long long

/*
sum<0なら0で終わり
そうじゃないなら？必ず正だけの地点を作る
根付き木にしてdp？
dp[i][j]:部分根付き木iまででj本切った時の最小値(iの上の部分を切ることも考えてる)
葉lに対してdp[l][0]=v[l],dp[l][1]=0;
親pに対して子をsとして、dp[p][k]=それぞれのそれ
つまり？dp[p][k]はmin(sum(dp[s][j]:sum j = k))
高速化の方法は？
えーなんかdpまでは絶対に合っていると思っていて、
+のやつを取るところに関しては+をなるべく大量に消費して欲しくて、マイナスは一個も入っちゃいけない
-が入る方は、+にならないようになるべく大きく取りたい
実は葉から貪欲？最後にくっつけられるところはくっつけていいとか
根付き木にして、葉から貪欲にやっていく　具体的には
・スタートが+の時
+で有る限り取り続ける
-が来た場合、-が大きくなるなら取って変更
・スタートが-の時
合計が負で有る限り取り続ける

貪欲が終わった時塊は　a:全部+で次の-入れても+ or b:合計が負で次のやつで+
・貪欲をした後
自分の子とくっつけられるかを試す？
自分がaの時　aは子の候補にいなくて、bは絶対無理　なので終わり
自分がbの時　子のbは全部取り込める
　　　　　　 これで子のaも取り込めるし次の+も取り込める場合は？
　　　　　　　・子のaを取り込むメリット:そのさらに子を取り込める可能性が出てくる
まってそもそも合流を考えられていない　いやそれは大丈夫かも
*/
int dp1[5123][5123],//dp1[v][k]:v以下k個、vのところは全部正の時のその最小値
    dp2[5123][5123],//dp2[v][k]:v以下k個、vの所の最小値
    dpt1[5123][5123],//dpt1[i][j]:vのi番目までの子までからなる部分木をdp1と同様にj個に分ける時の一番上の最小値
    dpt2[5123][5123];//dpt2[i][j]:同様

const int INF=1e18;

template<typename T>
void fin(T a){
  cout<<a<<endl;
  exit(0);
}

template<typename T>
void chmin(T &a,T b){
  if(a>b)a=b;
}
template<typename T>
void chmax(T &a,T b){
  if(a<b)a=b;
}

signed main(){
  int n;cin>>n;
  vector<int> v(n);
  for(int i=0;i<n;i++)cin>>v[i];
  vector<int> edge[n];
  for(int i=1;i<n;i++){
    int u,v;cin>>u>>v;u--;v--;
    edge[u].push_back(v);
    edge[v].push_back(u);
  }

  int sum=0;for(int i=0;i<n;i++)sum+=v[i];if(sum<0)fin(0);

  int r;int m=0;
  for(int i=0;i<n;i++)
    if(m<edge[i].size())
      m=edge[i].size(),r=i;

  vector<int> parent(n),son[n],d1(n,0),d2(n,0);
  parent[r]=-1;
  queue<int> que;
  que.push(r);
  while(que.size()){
    int p=que.front();que.pop();
    for(int q:edge[p]){
      if(parent[p]==q)continue;
      parent[q]=p;
      son[p].push_back(q);
      que.push(q);
    }
  }

  for(int i=0;i<5123;i++)
    for(int j=0;j<5123;j++)
      dp1[i][j]=dp2[i][j]=dpt1[i][j]=dpt2[i][j]=INF;

  vector<int> rem(n);
  for(int i=0;i<n;i++)rem[i]=son[i].size();
  for(int i=0;i<n;i++)if(!rem[i])que.push(i);

  while(que.size()){
    int p=que.front();que.pop();

    dpt1[0][0]=0;

    int N=son[p].size();

    int nowj=0,tmpj=0;
    for(int i=0;i<N;i++){
      int q=son[p][i];
      for(int j=0;j<=nowj;j++){
        if(dpt1[i][j]==INF)continue;
        for(int k=0;k<=d1[q];k++){
          if(dp1[q][k]==INF)continue;
          chmin(dpt1[i+1][j+k],dpt1[i][j]+dp1[q][k]);
          chmax(tmpj,j+k);
        }
        dpt1[i][j]=INF;
      }
      nowj=tmpj;
      tmpj=0;
    }

    for(int k=0;k<=nowj;k++){
      if(dpt1[N][k]==INF)continue;
      if(v[p]>0){
        chmin(dp1[p][k],dpt1[N][k]+v[p]);
        chmax(d1[p],k);
        chmin(dp2[p][k+1],0LL);
        chmax(d2[p],k+1);
      }
      dpt1[N][k]=INF;
    }

    dpt2[0][0]=0;

    nowj=tmpj=0;
    for(int i=0;i<N;i++){
      int q=son[p][i];
      for(int j=0;j<=nowj;j++){
        if(dpt2[i][j]==INF)continue;
        for(int k=0;k<=d2[q];k++){
          if(dp2[q][k]==INF)continue;
          chmin(dpt2[i+1][j+k],dpt2[i][j]+dp2[q][k]);
          chmax(tmpj,j+k);
        }
        dpt2[i][j]=INF;
      }
      nowj=tmpj;
      tmpj=0;
    }

    for(int k=0;k<=nowj;k++){
      if(dpt2[N][k]==INF)continue;
      chmin(dp2[p][k],dpt2[N][k]+v[p]);
      chmax(d2[p],k);
      if(dpt2[N][k]+v[p]<0){
        chmin(dp1[p][k+1],0LL);
        chmax(d1[p],k+1);
        chmin(dp2[p][k+1],0LL);
        chmax(d2[p],k+1);
      }
      dpt2[N][k]=INF;
    }
    if(~parent[p]&&--rem[parent[p]]==0)que.push(parent[p]);
  }

  int ans=INF;
  for(int j=0;j<5000;j++){
    if(dp1[r][j]!=INF)chmin(ans,j);
    if(dp2[r][j]<0)chmin(ans,j);
  }

  cout<<ans<<endl;
}

/*
解説AC
アイデア的には合っていた　根付き木にして管理して
ただ実装面しっかり詰められていなかったしそもそも計算量的に無理だと思っていた
各親について見るのか自分の下にいるもの分しかなくてO(N^2)っぽい？
どうやら二乗の木DPというやつらしい
要勉強
*/


Measured memory_bytes: 821120