HDU - 5977 Garden of Eden 【高维前缀和 + 点分治】

传送门
// 一棵树, 上面都有一个点权, 问有多少条路径满足进过的点的类型刚好有k种,
// 首先这个肯定是点分治, 但是难点和POJ那题的不同处就是这个是状态, 那么就不能像POJ那题进行排序选点了, 所以我们应该要换一种比较高效的方法. 首先是k只有10, 那么我们进行状压, 那么我们要求的就是路径上这条路与( | )起来等于( 1<< k) - 1, 那么我们肯定不能n方的找出两条路径来判断是否满足这个条件, 而应该是利用高位前缀和的思想.
因为我们要求的是某一条路径和已知的路径或起来的值, 即是已知路径中的二进制表示中只要为0的地方是1就行了. 所以就用到了高位前缀和, 比如dp[100] = dp[100] + dp[101] + dp[110] + dp[111]; 这样我们就可以直接算出答案从而优化复杂度. 细节请看代码.

AC Code

const int maxn = 5e4+5;
int n, cnt, head[maxn], k, vis[maxn], root, maxx, dis[maxn];
int num, tot, siz[maxn], mv[maxn];
int a[maxn];
struct node {
    int to, w, next;
} e[maxn<<1];

void add(int u, int v, int w)
{
    e[cnt] = (node){v,w,head[u]};
    head[u] = cnt++;
}

void getroot(int u, int fa)
{
    siz[u] = 1, mv[u] = 0;
    for (int i = head[u]; ~i; i = e[i].next) {
        int to = e[i].to;
        if (to == fa || vis[to]) continue;
        getroot(to, u);
        siz[u] += siz[to];
        mv[u] = max(mv[u], siz[to]);
    }
    mv[u] = max(mv[u], tot - siz[u]);
    if (mv[u] < mv[root]) root = u;
}
int dp[1030]; //这个不能开太大,否则会T.
void getdis(int u,int fa,int dep)
{
    dp[dep]++;
    dis[++num] = dep;
    for (int i = head[u]; ~i; i = e[i].next) {
        int to = e[i].to;
        if (to == fa || vis[to]) continue;
        getdis(to, u, dep | (1 << a[to]));
    }
}
ll ans;

ll cal(int u,int f)
{
    num = 0; Fill(dp,0);
    getdis(u,-1,f | (1 << a[u]));
    for(int i=0;ifor(int j = 0 ; j < (1<if(!((1<1 << i)];
        }
    }
    ll res = 0;
    for(int i=1;i<=num;i++){
        int tmp = dis[i];
        tmp ^= (1<1;
        res += dp[tmp];
    }
    return res;
}

void work(int u)
{
    vis[u] = 1;
    ans += cal(u, 1 << a[u]);
    for (int i = head[u]; ~i; i = e[i].next) {
        int to = e[i].to;
        if (vis[to]) continue;
        ll tmp = cal(to, 1 << a[u]);
        ans -= tmp;
        mv[root=0] = tot = siz[to];
        getroot(to, -1);
        work(root);
    }
}

void solve()
{
    while(~scanf("%d%d",&n,&k)){
        cnt = 0 ; Fill(head,-1); Fill(vis,0);
        for(int i=1;i<=n;i++) {
            scanf("%d",&a[i]);
            a[i]--;
        }
        for (int i = 1; i < n; i++) {
            int u, v, w;
            scanf("%d%d",&u,&v);
            add(u, v, 0); add(v, u, 0);
        }
        ans = 0;
        mv[root=0] = tot = n;
        getroot(1, -1);
        work(root);
        cout << ans << endl;
    }
}

你可能感兴趣的:(树的点分治)