HDU 1530

这道题 是一个 最大团板子题。。。虽然才刚刚开始看最大团

 

 

首先什么是最大团,假设一个图里,有一些点和边构成一个子图,他们不与外界交流,完全就是个独立的个体,我们就可以叫他们是一个团,也就是 完全图,

最大团 通俗的讲,就是在一个图里找一个点数最多的 完全子图

那么因为只需要统计团中 节点的个数,那么 我们需要做的,就是 dfs 遍历一次所有连通的点,不断记录最大的团数目,直至搜索完整个图。

网上大牛们 有一些思路: (链接https://blog.csdn.net/weixin_37806707/article/details/60961435

最暴力的方法:

将n个点加入一个集合U1,遍历U1中的点,每次选择1个点P1作为最大团的第一个点,即DFS的第一层。

遍历U1中剩下的点,选出与P1相连的点,加入集合U2,每次选择U2中的一个点P2作为最大团的第二个点,即DFS的第二层。

遍历U2中剩下的点,选出与P2相连的点,加入集合U3,每次选择U3中的一个点P3作为最大团的第三个点,即DFS的第三层(因为团即最大完全子图,要求各个点两两相连,因此只要从之前的集合中选择点即可,只有这些点是与之前的点相连的)。

……

以此类推,到DFS的最后一层m,即此时的Um为空集时,递归结束,求出了一个团。返回上一层继续递归其他情况……选出其中的最大团输出。
 

以上是最朴素的思路,但是这样的时间复杂度太高了,所以我们考虑剪枝:

1.显然,当DFS进行到某一层x时,如果当前层数(即团内点的数量)加上集合Ux中剩下点的数量小于等于已得出的答案(即使把剩下的点全部加入团中也无法更新答案),那我们可以直接返回上一层。

2.倒序遍历,采用DP进行优化,DP[i]表示第i个及之后的点所能组成的最大团。若当前层数为x,剩下的点从i开始,那么如果 x+DP[i]<=ans ,那么同样可以直接返回。
 

以下是 只进行稍微剪枝的暴力法代码

 

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int MAXN=205;

int n;
int ed[MAXN][MAXN];
int ses[MAXN];
int ans;

bool check(int endd,int x)
{
    for( int i=1; i<endd; ++i )
    {
        if( !ed[ ses[i] ][x] )
        {
            return false;
        }
    }
    return true;
}

void dfs(int dis, int now)
{
    if( dis+n-now+1 <= ans ) //剪枝
    {
        return;
    }
    for( int i=now; i<=n; ++i )
    {
        if(check(dis+1, i))
        {
            ses[dis+1]=i;
            dfs(dis+1, i+1);
        }
    }
    ans=max(ans, dis);
}

int main()
{
    while(~scanf("%d", &n) && n )
    {
        for( int i=1; i<=n; ++i )
        {
            for( int j=1; j<=n; ++j )
            {
                scanf("%d", &ed[i][j]);
            }
        }
        ans=0;
        dfs(0, 1);
        printf("%d\n", ans);
    }
    return 0;
}

当然 这样确实 暴力一些 虽然比较容易理解,虽然加入初步的剪枝可以过,但是还是不够优秀。

之后我们引入以下优化方法:

倒叙记录之前所有点组成的团中点数目 可参照 dp 剪枝那里。

 

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int MAXN=205;

int n;
int ed[MAXN][MAXN];
int ses[MAXN];
int ans;
int dp[maxn]; //加入dp数组 dp[i] 表示 i 以后能组成的最大团

bool check(int endd,int x)
{
    for( int i=1; i<endd; ++i )
    {
        if( !ed[ ses[i] ][x] )
        {
            return false;
        }
    }
    return true;
}

void dfs(int dis, int now) // dis 表示层数 now 表示当前点
{
    if( dis+n-now+1 <= ans || dis+dp[now] <= ans) //剪枝
    {
        return;
    }
    for( int i=now; i<=n; ++i )
    {
        if(check(dis+1, i))
        {
            ses[dis+1]=i;
            dfs(dis+1, i+1);
        }
    }
    ans=max(ans, dis);
}

int main()
{
    while(~scanf("%d", &n) && n )
    {
        for( int i=1; i<=n; ++i )
        {
            for( int j=1; j<=n; ++j )
            {
                scanf("%d", &ed[i][j]);
            }
        }
        memset(dp,0,sizeof(dp));
        dp[n] = 1;
        ans = 0;
        for(int i=n-1; i>=1; i--)
        {
            ses[1] = i;
            dfs(1, i+1);
            dp[i] = ans;//不断更新 dp 
        }
        printf("%d\n", dp[1]);
    }
    return 0;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值