HDU - 5977,点分治,状压

本文深入探讨了点分治算法在解决特定类型图论问题中的应用。通过分析一棵树上节点类型的全集路径问题,详细讲解了如何使用点分治算法有效计算所有可能的路径组合。文章首先介绍了点分治的基本概念,随后通过一个具体的编程实现案例,展示了如何利用点分治算法解决实际问题。此外,还特别关注了算法的复杂度分析,确保解决方案的高效性。
摘要由CSDN通过智能技术生成

Garden of Eden

https://vjudge.net/problem/550645/origin
When God made the first man, he put him on a beautiful garden, the Garden of Eden. Here Adam lived with all animals. God gave Adam eternal life. But Adam was lonely in the garden, so God made Eve. When Adam was asleep one night, God took a rib from him and made Eve beside him. God said to them, “here in the Garden, you can do everything, but you cannot eat apples from the tree of knowledge.”
One day, Satan came to the garden. He changed into a snake and went to live in the tree of knowledge. When Eve came near the tree someday, the snake called her. He gave her an apple and persuaded her to eat it. Eve took a bite, and then she took the apple to Adam. And Adam ate it, too. Finally, they were driven out by God and began a hard journey of life.
The above is the story we are familiar with. But we imagine that Satan love knowledge more than doing bad things. In Garden of Eden, the tree of knowledge has n apples, and there are k varieties of apples on the tree. Satan wants to eat all kinds of apple to gets all kinds of knowledge.So he chooses a starting point in the tree,and starts walking along the edges of tree,and finally stops at a point in the tree(starting point and end point may be same).The same point can only be passed once.He wants to know how many different kinds of schemes he can choose to eat all kinds of apple. Two schemes are different when their starting points are different or ending points are different.

题意:给你一颗有n个点的树,每个节点有一个type值,问存在多少条路径使得路径上的所有type值是一个全集
思路:首先只有10种type,将每个点的type状压成1<<type,之后进行点分治,每次将所有点到重心的type或起来(之前是算的距离,这题将点值或起来)得到一个dis数组,然后需要计算多少组(dis[i],dis[j])或起来是满状态,一开始我是直接记录每种状态出现的次数,然后两个for循环看哪两个状态或起来是满状态,复杂度1<<20以为是够的,但注意点分治是保证每一层的复杂度是O(n),即每一层会遍历n个点,但每一层会对每个子树都进行一次计算,故这样写复杂度是不够的,由于路径数和点数相同,故可以直接枚举每一个路径的子集,具体见代码中的cal函数,这样复杂度是O(nlogn * (1<<k))

#include<bits/stdc++.h>
#define MAXN 50010
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
int head[MAXN],tot;
struct edge
{
    int v,nxt;
}edg[MAXN << 1];
inline void addedg(int u,int v)
{
    edg[tot].v = v;
    edg[tot].nxt = head[u];
    head[u] = tot++;
}
int n,m,root,ms,mson[MAXN],sz[MAXN],Size;
bool vis[MAXN];
//root用于标记重心,ms表示树的重心的最大子树的大小,mson[i]记录以i为根最大子树的大小
//sz[i]记录以i为根子树的大小,Size表示当前整棵树的大小,vis[i]表示当前节点是否被分治过
void getroot(int u,int f)//获得重心
{
    sz[u] = 1,mson[u] = 0;
    int v;
    for(int i = head[u];i != -1;i = edg[i].nxt)
    {
        v = edg[i].v;
        if(vis[v] || v == f) continue;//剔除已经被分治过的点
        getroot(v,u);
        sz[u] += sz[v];
        if(sz[v] > mson[u]) mson[u] = sz[v];
    }
    if(Size - sz[u] > mson[u]) mson[u] = Size-sz[u];//把u看作根节点时u的父亲那一部分也算作子树
    if(ms > mson[u]) ms = mson[u],root = u;//更新重心
}
int val[MAXN];
ll ans;
int dis[MAXN],cnt;//dis记录所有节点到重心的距离
int num[1<<10];//记录每种状态出现的次数
void getdis(int u,int f,int d)//获得到目标点路径上的状态或
{
    dis[++cnt] = d;
    int v;
    for(int i = head[u];i != -1;i = edg[i].nxt)
    {
        v = edg[i].v;
        if(vis[v] || v == f) continue;
        getdis(v,u,d | val[v]);
    }
}
void cal(int u,int d,int tp)//u表示getdis的起点,d表示u到目标点的距离,tp表示这一次统计出来的答案是合理的还是不合理的
{
    cnt = 0;
    getdis(u,0,d|val[u]);//算出树中的点到目标点的距离
    memset(num,0,sizeof(int)*(m+1));
    for(int i = 1;i <= cnt;++i)
        for(int j = dis[i];j;j = (j-1)&dis[i]) //枚举子集,特别注意这里,因为路径数等于子树的点数,故每层只有n,但如果暴力(1<<m)*(1<<m)枚举复杂度没有保障
            ++num[j];
    num[0] = cnt-1;
    for(int i = 1;i <= cnt;++i)
        ans += num[dis[i]^m]*tp;
}
void solve(int u,int ssize)//ssize是当前这棵子树的大小
{
    vis[u] = true;//代码保证每次进来的u都必定是当前这棵树的重心,我们将vis[u]标记为true,表示u点被分治过
    cal(u,0,1);//计算这棵树以u为重心的所有组合,但包括了共用同一条边的情况
    int v;
    for(int i = head[u];i != -1;i = edg[i].nxt)
    {
        v = edg[i].v;
        if(vis[v]) continue;
        cal(v,val[u],-1);//将共用一条边的不合法情况去除
        ms = INF;//记得每次都要初始化
        Size = sz[v] < sz[u]?sz[v]:(ssize-sz[u]);//因为v实际上可能是u的父亲,故sz需相减
        getroot(v,v);//求出以v为根节点的子树重心
        solve(root,Size);
    }
}
inline void init()
{
    tot = 0,ms = INF,Size = n;
    memset(head,-1,sizeof(int)*(n+1));
    memset(vis,false,sizeof(bool)*(n+1));
    ans = 0;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        int tmp;
        m = (1<<m)-1;
        int xx = 0;
        for(int i = 1;i <= n;++i)
        {
            scanf("%d",&tmp);
            val[i] = (1<<(tmp-1));
            if(val[i] == m)
                ++xx;
        }
        int u,v;
        for(int i = 1;i < n;++i)
        {
            scanf("%d%d",&u,&v);
            addedg(u,v),addedg(v,u);
        }
        getroot(1,1);
        solve(root,Size);
        printf("%lld\n",1ll*ans+xx);
    }
    return 0;//
}
/*
6 4
1 2 2 3 2 4
1 2
3 5
3 6
1 3
1 4

5 1
1 1 1 1 1
1 2
2 3
3 4
4 5
 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值