树的最小支配集&&最小点覆盖&&最大独立集模板

最小支配集模板 

/*
最小支配集:从V中取尽量少的点组成一个集合,使得V中剩余的点都与取出来的边相连
覆盖i,要么是父节点覆盖,要么是自己,要么是孩子,所以三种状态(和上面的对应):             
1、点i自己覆盖自己,i的所有孩子都覆盖,用dp[i][0]表示;
2、点i被自己的孩子覆盖,i和i的所有孩子都覆盖,用dp[i][1]表示;
3、点i被父节点覆盖,i的所有孩子都覆盖,用dp[i][2]表示;
那么动态转移方程就是(v是i的孩子):
dp[i][0]+=min(dp[v][0],dp[v][1],dp[v][2]);
dp[i][2]+=min(dp[v][0],dp[v][1]);
对于dp[i][1],要考虑全面,也就是说:必须要有一个孩子建塔,才能保证i被覆盖(Min=sum(min(dp[v][0]-dp[i][1])),也就是当所有孩子的dp[v][0]>dp[v][i]时,Min表示他们差值最小的那个差值)。
所以方程是dp[i][1]+=min(dp[v][0],dp[1])(至少存在一个孩子的dp[v][0]<=dp[v][1],否则要dp[i][1]+=Min);
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define M 10007
#define inf 0x3f3f3f
using namespace std;
int dp[M][3];
int head[M],k,n;
bool vis[M];

struct sa{
    int v,next;
}edg[M*2];

void addedge(int u,int v)
{
    edg[k].v=v;
    edg[k].next=head[u];
    head[u]=k++;
}

void dfs(int key)
{
    bool flag=true;
    vis[key]=false;
    dp[key][0]=1;
    dp[key][1]=dp[key][2]=0;
    int minn=inf;
    for(int i=head[key];i!=-1;i=edg[i].next)
    {
        int v=edg[i].v;
        if(vis[v])
        {
            dfs(v);
            dp[key][0]+=min(dp[v][0],min(dp[v][1],dp[v][2]));
            dp[key][2]+=min(dp[v][0],dp[v][1]);
            if(dp[v][0]<=dp[v][1])
            {
                flag=false;
                dp[key][1]+=dp[v][0];
            }
            else
            {
                dp[key][1]+=dp[v][1];
                minn=min(minn,dp[v][0]-dp[v][1]);
            }
        }
    }
        if(flag)
            dp[key][1]+=minn;
}

int main()
{
    //freopen("in.txt","r",stdin);
    while(scanf("%d",&n)!=EOF)
    {
        memset(vis,true,sizeof(vis));
        memset(head,-1,sizeof(head));
        k=1;
        int a,b;
        建图,添加双向边
        dfs(1);
        printf("%d\n",min(dp[1][0],dp[1][1]));
    }
    return 0;
}

最小点覆盖模板:

/*
最小点覆盖:从V中取尽量少的点组成一个集合,使得E中所有边都与取出来的点相连。
dp[i][0]表示点i属于点覆盖,并且以点i为根的子树中所连接的边都被覆盖的情况下点覆盖集中所包含最少点的个数。
dp[i][1]表示点i不属于点覆盖,且以i为根的子树中所连接的边都被覆盖的情况下点覆盖集中所包含最少点的个数。
对于第一种状态dp[i][0],等于每个儿子节点的两种状态的最小值之和+1,方程为:dp[i][0]=1+∑(p[u]=i)min(dp[u][0],dp[u][1]).
对于第二种状态dp[i][1],要求所有与i连接的边都被覆盖,但是i点不属于点覆盖,那么i点所有孩子子节点都必须属于覆盖点,即对于点i的第二种状态与所有子节点的第一种状态有关,在树枝上等于所有子节点的第一种状态的和,方程为:dp[i][1]=∑(p[u]=i)dp[u][0].
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define M 1507
using namespace std;
int head[M],nodes;
int dp[M][M];
struct E
{
    int v,next;
}edge[M*M];
void addedge(int u,int v)
{
    edge[nodes].v=v;edge[nodes].next=head[u];
    head[u]=nodes++;
}
void DP(int u,int p)
{
    dp[u][0]=1;
    dp[u][1]=0;
    int k,v;
    for(k=head[u];k!=-1;k=edge[k].next)
    {
        v=edge[k].v;
        if(v==p)continue;
        DP(v,u);
        dp[u][0]+=min(dp[v][0],dp[v][1]);
        dp[u][1]+=dp[v][0];
    }
}
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        memset(head,-1,sizeof(head));
        nodes=0;
        int u,v;
      建图,双向边
        DP(0,0);
        printf("%d\n",min(dp[0][0],dp[0][1]));
    }
    return 0;
}

最大点独立集

/*
最大独立点集:从V中取尽量多的点组成一个集合,使得这些点之间没有边连边。
dp[i][0]表示点i属于独立集的情况下,最大独立集中点的个数。
dp[i][1]表示点i不属于独立集的情况下,最大独立集中点的个数。
对于第一种状态dp[i][0],由于i点属于独立集,它的子节点都不能属于独立集,所以对于点i的第一种状态,只有子节点的第二种状态有关,等于其所有的子节点的第二种状态的值的和加1,方程为:dp[i][1]=1+∑(p[u]=i)dp[u][1]。
对于第二种状态dp[i][1],由于点i不属于独立集,所以点i的子节点可以属于独立集,也可以不属于独立集,所取的子节点的状态应该是所表示的独立集个数较大的那个,方程为:dp[i][1]=∑(p[u]=i)max(dp[u][0],dp[u][1])。
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define M 1507
using namespace std;
int head[M],nodes;
int dp[M][M];
struct E
{
    int v,next;
}edge[M*M];
void addedge(int u,int v)
{
    edge[nodes].v=v;edge[nodes].next=head[u];
    head[u]=nodes++;
}
void DP(int u,int p)
{
    dp[u][0]=1;
    dp[u][1]=0;
    int k,v;
    for(k=head[u];k!=-1;k=edge[k].next)
    {
        v=edge[k].v;
        if(v==p)continue;
        DP(v,u);
        dp[u][0]+=dp[v][1];
        dp[u][1]+=max(dp[v][0],dp[v][1]);
    }
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(head,-1,sizeof(head));
        nodes=0;
        int u,v;
        建图,双向图
        DP(0,0);
        printf("%d\n",max(dp[0][0],dp[0][1]));
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值