acwing 3725.构造完全图(状压dp)

acwing 3725.构造完全图(南昌理工学院)

1.题目介绍

acwing周赛第6场第三题题目传送门
给定一个由 n 个点和 m 条边构成的无向连通图。

我们希望通过一系列操作将其变为一个完全图(即每对不同的顶点之间都恰有一条边相连)。

每次操作时,可以选择其中一个点,找到所有和它直接相连的点,使这些点两两之间连边(若两点之间已经存在边,则无需重复连接)。

请问,至少多少次操作以后,可以将整个图变为一个完全图?

输入格式
第一行包含两个整数 n,m。

接下来 m 行,每行包含两个整数 u,v,表示点 u 和点 v 之间存在一条边。

所有点编号 1∼n。

输出格式
第一行输出最少操作次数。

第二行输出每次操作所选的点的编号,整数之间空格隔开。如果最少操作次数为 0,则无需输出第二行。

如果答案不唯一,则输出任意合理方案均可。

数据范围
前三个测试点满足 1≤n,m≤10。
所有测试点满足 1≤n≤22,0≤m≤n(n−1)2,1≤u,v≤n,u≠v。
保证没有重边和自环,保证给定图是连通的。

输入样例1:
5 6
1 2
1 3
2 3
2 5
3 4
4 5
输出样例1:
2
2 3
输入样例2:
4 4
1 2
1 3
1 4
3 4
输出样例2:
1
1
难度:困难
时/空限制:2s / 256MB
总通过数:110
总尝试数:322
来源:AcWing,第6场周赛
算法标签

2.解题思路

2.1 解题前先看眼数据范围n最大是22,数据不大,可以用的算法有很多,根据题目难度和y总的由数据范围反推算法复杂度以及算法内容可以断定不是搜索就是状压dp。
2.2 通读题目可知,给定n个点和m条边,每次可以选择一个点,把它所连的所有点都两两相连,要求出构成完全图的最小步数选择的所有点
2.3 假定这题要用到的算法是搜索,那么每次连接未走过的点,每次有选与不选两种方案,总而言之,搜索是构造一个1到n全排列加上每个点选与不选的所有可能,总的时间复杂度为(n! * 2 ^ n),代入22的话比1e8大很多,所以搜索不行。
2.4 假定这题要用的算法是状压dp,从数据上来看是可行的,接着来分析可行性,状压dp第一步要枚举出n个点选与不选的所有状态S(2^n),
第二步从当前状态S中选择一个当前状态包含的点与它能连的所有边连一下,得出一个新的状态T,判断S -> T是否更优(花费的步数更小),若更优则进行更新并进行记录。
从算法角度可行,在从时间复杂度来看,枚举所有状态)(2^n),每个状态依次枚举它所包含的所有的点O(n), 总的时间复杂度(n * 2^n),代入22的话是大概是1e8左右,给的时间是2秒,完全ok,具有可行性。

3.代码实现

#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;

typedef pair<int, int> P;
const int N = 22, M = 1 << 22, inf = 1e9 + 7;

int n, m, u, v;
int e[N];//记录每个点能连的所有点
int f[M];//状态数组,记录到达每种状态的最小代价
P g[M];// 标记数组,下标是当前状态,fist 记录到达当前状态之前的状态,second记录当前到达形状所练接的点

int main()
{
    cin >> n >> m;
     if (m == n * (n - 1) / 2) //特判n = 1, m = 0 的情况
    {
        puts("0\n");
        return 0;
    }

    memset(f, inf, sizeof f);//初始化到达所有状态的代价是无穷大
    for (int i = 0; i < n; ++i) e[i] = 1 << i; //记录自己到自己
    for (int i = 0; i < m; ++i) {//记录输入的点能到的所有的点
        cin >> u >> v;
        u--; v--;
        e[u] |= 1 << v;
        e[v] |= 1 << u;
    }
    //                         从当前点连接一次后的状态  记录状态
    for (int i = 0; i < n; ++i) f[e[i]] = 1, g[e[i]] = {0, i};
    for (int S = 0; S < 1 << n; ++S) { //枚举所有状态
        if (f[S] == inf) continue; 
        for (int j = 0; j < n; ++j) {
            if (S >> j & 1) { // 当前状态中有j点
                if (f[S | e[j]] > f[S] + 1) { // 从当前状态 S 到 S | e[j]
                    f[S | e[j]] = f[S] + 1;
                    g[S | e[j]] = {S, j}; 
                }
            }
        }
    }
    int k = (1 << n) - 1; // 最后所有点都被包含的状态
    cout << f[k] << endl; 
    while (k) { // 输出路径
        cout << g[k].y + 1 << ' ';
        k = g[k].x;
    }
    return 0;
}




  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值