#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define int ll

#define REP(i,n) for(int i=0;i<n;++i)
#define SORT(name) sort(name.begin(), name.end())
#define ZERO(p) memset(p, 0, sizeof(p))
#define MINUS(p) memset(p, -1, sizeof(p))
#if 1
#  define DBG(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#  define DBG(fmt, ...)
#endif

const ll LLINF = (1LL<<60);
const int INF = (1LL<<30);
const double DINF = std::numeric_limits<double>::infinity();
const int MOD = 1000000007;
#define MAX_N 5010

ll N;
vector<ll> A;
vector< vector<ll> > G; // グラフ(隣接リスト)

// 頂点 v を根とした部分木に何個の頂点が含まれるか(自分含む)
ll num[MAX_N];

// dp[v][num][0]
// v が根。 num 本の辺を切断する場合 、かつ、
// v を含まない連結成分は条件を満たす場合の、
// v を含む連結成分の重みの総和の最小
// dp[v][num][1]
// v が根。 num 本の辺を切断する場合、かつ、
// v を含まない連結成分は条件を満たす場合、かつ、
// v を含む連結成分の重みがすべて正の場合の、総和の最小
ll dp[MAX_N][MAX_N][2];

// 頂点 v の子ノードに対する dp
// dp[][][] を作成するために各頂点ごとに初期化して利用する一時的なテーブル
// sdp[num][0]
// num 本の辺を切断する場合 、かつ、
// v を含まない連結成分は条件を満たす場合の、
// v を含む連結成分の重みの総和の最小
// sdp[num][1]
// num 本の辺を切断する場合、かつ、
// v を含まない連結成分は条件を満たす場合、かつ、
// v を含む連結成分の重みがすべて正の場合の、総和の最小
ll sdp[MAX_N][2];

// v: 頂点 id
// parent: v の前に辿っていた頂点(親)
// dp[] を葉から順に埋めていく
void rec(ll v, ll parent) {
    // num[v] の作成
    num[v] = 1;
    for(auto& child : G[v]) {
        if(child == parent) { continue; }
        rec(child, v);
        num[v] += num[child];
    }

    // sdp 初期化
    for(ll j = 0; j <= num[v]; ++j) { sdp[j][0] = sdp[j][1] = LLINF; }

    sdp[0][0] = A[v];
    if(A[v] > 0) { sdp[0][1] = A[v]; }

    // 現在までに見た部分木のノード数の和(= 今までの切断可能辺数)
    ll curnum = 0;

    // 頂点 v の子である頂点 child について考える
    for(auto child : G[v]) {
        if(child == parent) { continue; }

        for(ll j = curnum; j >= 0; --j) {
            ll tmp0 = sdp[j][0];    // v 以下の部分木で切断辺数が j のときの、総和の最小値
            ll tmp1 = sdp[j][1];    // v 以下の部分木で切断辺数が j のときの、総和の最小値(重みが正のみ)
            sdp[j][0] = sdp[j][1] = LLINF;

            // k : 切断可能辺数
            for(ll k = 0; k < num[child]; ++k) {
                // dp[child][k][0] : 頂点 child 以下の部分木から k 個の辺を切断したときの
                //                   連結成分の重みの総和の最大値
                sdp[j+k][0] = min(sdp[j+k][0], tmp0 + dp[child][k][0]);

                // もし総和が機能停止の条件を満たす
                //   (負か重みが正のみの総和も値が入っている(LLINF じゃない) )
                // ときは、全てを切り離した際の値も更新できる
                if(dp[child][k][0] < 0 || dp[child][k][1] < LLINF / 2) {
                    sdp[j+k+1][0] = min(sdp[j+k+1][0], tmp0);
                }

                // 重みが正なら、正のみの dp テーブルも更新
                if(A[v] > 0) {
                    sdp[j+k][1] = min(sdp[j+k][1], tmp1 + dp[child][k][1]);
                    if(dp[child][k][0] < 0 || dp[child][k][1] < LLINF/ 2) {
                        sdp[j+k+1][1] = min(sdp[j+k+1][1], tmp1);
                    }
                }
            }
        }
        // この child 以下の辺の数 + v と child をつなぐ辺 = num[child]
        // なので curnum として保持しておく
        curnum += num[child];
    }

    // sdp の dp への反映
    for(ll j = 0; j <= num[v]; ++j) {
        dp[v][j][0] = sdp[j][0];
        dp[v][j][1] = sdp[j][1];
    }
}

signed main()
{
    cin >> N;
    A.resize(N);
    REP(i, N) { cin >> A[i]; }
    G.assign(N, vector<ll>());
    REP(i, N-1) {
        ll u, v;
        cin >> u >> v;
        --u; --v;
        G[u].push_back(v);
        G[v].push_back(u);
    }

    // DP 初期化
    REP(i, MAX_N) { REP(j, MAX_N) { dp[i][j][0] = dp[i][j][1] = LLINF; } }
    // 0 を根とした dp テーブルの作成
    rec(0, -1);

    // ans を求める
    ll ans = N;
    for(ll i = 0; i <= N; ++i) {
        // 0 を根として、 i 回切断したときに条件が満たされるなら ans を更新
        if(dp[0][i][0] < 0) { ans = min(ans, i); }
        if(dp[0][i][1] < LLINF / 2) { ans = min(ans, i); }
    }
    printf("%lld\n", ans);
    return 0;
}


Measured memory_bytes: 393344