最大团的求法

先膜拜+感谢owen,QW。

今天用了很多的时间研究了一道状态压缩搜索题。

求最大团的个数及方案数。

组队(mc) 
【问题描述】 
 
小秋秋想出去玩了。。 
小秋秋有许多朋友,有一些小秋秋的朋友相互之间也是朋友。。。 
小秋秋觉得自己带不是朋友的两个朋友出去玩会出现尴尬。。。(好纠结) 
小秋秋想知道自己最多可以带多少朋友出去玩以及带人最多的方案数。。 
 
【输入文件】(input.txt) 
 
第一行两个数,n,m 分别表示小秋秋的朋友数,以及他们之间相互认识的关
系对数。 
接下来 m 行,每行两个整数 x,y 表示朋友 x 和朋友 y 他们相互认识。 
 
【输出文件】(output.txt) 
 
一行两个整数,分别表示能选出一起出去玩得最大人数,以及能达到最大人
数的方案数。 
 
【样例输入】 
 
4 5 
1 2 
2 3 
3 1 
1 4 
2 4 
 
【样例输出】 
 
3 2 
 
【数据约定】 
 
test  0 1  2  3  4  5  6  7  8  9 
n     5 10 15 20 25 30 35 40 45 50 

虽然用二分图的知识(求最大独立集再取反)可以比较简单的求出最大团的个数,但是要求方案数就无能为例了。 


此题我一共写了三个版本。

一是爆搜。QW写了个dancing links优化

二是搜索选点组合,再利用状态压缩优化最大团的判断。

三是搜索选点组合+最优化剪枝、缩小上界,再利用状态压缩优化最大团的判断。

另吐槽下蒯个标程当题解发出来的童鞋。

 

注:

为便于叙述,不妨将满足本题中要求的子图叫做团。(实际上团的概念并非如此)

 

然后我对每个代码里有意义部分进行下解释吧。


code1

#include <cstdio>
#include <iostream>
using namespace std;

const int maxn = 50 + 5;

int n, m;
int tmp[maxn];
int map[maxn][maxn];
int nmax(-INT_MAX), tmax(-INT_MAX);

void check(int v), dfs(int u, int v);

int main()
{
   freopen("input.txt", "r", stdin);
   freopen("output.txt", "w", stdout);

   cin >> n >> m;
   for (int i = 1, x, y; i <= m; ++i)
      scanf("%d%d", &x, &y),
      map[x][y] = map[y][x] = 1;
   dfs(0, 0);
   cout << nmax << " " << tmax << endl;

   return 0;
}

void check(int v)
{
   bool mix(1);
   for (int i = 1; i <= v; ++i)
      for (int j = i + 1; j <= v; ++j)
         if (!map[tmp[i]][tmp[j]]) { mix = 0; break; }
   if (!mix) return;
   if (v > nmax) nmax = v, tmax = 1;
   else if (v == nmax) ++tmax;
}

void dfs(int u, int v)
{
   if (v >= nmax) check(v);
   if (u >= n || v >= n) return;
   for (int i = u + 1; i <= n; ++i)
   {
      tmp[v + 1] = i;
      dfs(i, v + 1);
   }
}

搜索没什么好讲的,而且没状压=  =。

然后 Orz cuizimu不状态压缩裸搜只要1 s!


code2

#include <cstdio>
#include <iostream>
using namespace std;

const int maxn = 50 + 5;

int n, m;
int tmp[maxn];
int map[maxn][maxn];
long long s[maxn];
int nmax(-INT_MAX), tmax(-INT_MAX);

void check(int v), dfs(int u, int v, long long sta);

int main()
{
   freopen("input.txt", "r", stdin);
   freopen("output.txt", "w", stdout);

   cin >> n >> m;
   for (int i = 1, x, y; i <= m; ++i)
      scanf("%d%d", &x, &y),
      map[x][y] = map[y][x] = 1;
   //预处理成二进制串
   for (int i = 1; i <= n; ++i)
   {
      long long sta = 1;
      for (int j = 1; j <= n; sta <<= 1, ++j)
         if (map[i][j] != 0) s[i] += sta;
   }
   dfs(0, 0, 0);
   cout << nmax << " " << tmax << endl;

   return 0;
}

void dfs(int u, int v, long long sta)
{
   if (u >= n) return;
   for (int i = u + 1; i <= n; ++i)
      if ((sta & s[i]) == sta)
      {
         if (v + 1 > nmax) nmax = v + 1, tmax = 1;
         else if (v + 1 == nmax) ++tmax;
         dfs(i, v + 1, sta | ((long long)1 << (i - 1)));//位运算记得要强制类型转换
      }
}

加上状态压缩优化。

状态压缩比较易懂,开long long强压邻接矩阵的结果s[i],即用一个二进制数串表示了i号点所连接的点,能直接相连则为1,不能直接相连则为0。如若2能与3, 4, 7, 8号点相连,则状态压缩后表示为 s[2] = 11001100 。


若当前状态(已经是团但不一定最大)为sta,若(sta & s[i]) == sta,则表示加入i点后仍能维护团的性质。然后将i点加入状态再继续搜索即可,sta | (1 << (i- 1))。记得位运算要强制类型转换!


code3

#include <cstdio>
#include <iostream>
using namespace std;

const int maxn = 50 + 5;

int n, m, nmax, tmax;
int num[maxn], map[maxn][maxn];
long long s[maxn];

void dfs(int u, int v, long long sta);

int main()
{
   freopen("input.txt", "r", stdin);
   freopen("output.txt", "w", stdout);

   cin >> n >> m;
   for (int i = 1, x, y; i <= m; ++i)
      scanf("%d%d", &x, &y),
      map[x][y] = map[y][x] = 1;

   //预处理成二进制串
   for (int i = 1; i <= n; ++i)
   {
      long long sta = 1;
      for (int j = 1; j <= n; sta <<= 1, ++j)
         if (map[i][j] != 0) s[i] += sta;
   }

   for (int i = n; i >= 1; --i)
   {
      dfs(i, 0 + 1, 0 | (1LL << (i - 1)));
      num[i] = nmax;
   }
   cout << nmax << " " << tmax << endl;

   return 0;
}

void dfs(int u, int v, long long sta)
{
   if (u >= n) return;
   for (int i = u + 1; i <= n - nmax + v + 1; ++i)//缩小上界 
      if ((sta & s[i]) == sta)
      {
         if (v + 1 > nmax) nmax = v + 1, tmax = 1;
         else if (v + 1 == nmax) ++tmax;
         if (v + 1 + num[i] < nmax) continue;
         dfs(i, v + 1, sta | (1LL << (i - 1)));//位运算记得要强制类型转换前面的数
      }
}

此代码写法与前两个感觉已经有本质区别了。

首先主程序中最开始是由n~1开始搜索的,每次搜索最开始的i必须选,目的是可以用第一个很强的最优性剪枝。



有些公式没法放进CSDN blog于是我就截图了


code3效率是不错的(0.5s)


但由于最初我是从 n~1 枚举的,因此有一个同样很强的优化用不上

但我提供下标程给读者思考吧

#include<cstdio>
long long e[55];
int f[55],n,m,i,j,k,s,ans,max;
void up(int x)
{
  if (x == max) ans++;
  else max = x, ans = 1;
}
void dfs(int x,int s,long long V,long long E)
{
     if ((V & E) != V || s + f[x] < max) return;
     if (x > n) { up(s); return; };
     dfs(x + 1, s + 1, V |(1LL << x - 1), E & (e[x]));
     dfs(x + 1, s, V, E);
}
int main()
{
   freopen("input.txt","r",stdin);
   freopen("output.txt","w",stdout);
   scanf("%d%d",&n,&m);
   for (; m; m--)
      scanf("%d%d", &i, &j), e[i] |= 1LL << j - 1, e[j] |= 1LL << i - 1;
   for (i = 1; i <= n; i++) e[i] |= 1LL << i - 1;
   for (i = n; i; i--)
   {
      dfs(i+1, 1, 1LL << i - 1, e[i]);
      f[i] = max;
   }
   printf("%d %d", max, ans);
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值