AT_abc412_d 题解
题目传送门
题面
给你一张简单无向图, 要求你做以下两种操作若干次
操作1: 删除一条边
操作2: 选择两个不同的点,增加一条边
请问最少需要操作多少次, 才能使最后每一个节点的度都为2
注: 操作时不可以出现重边或自环(即每一次操作过后这张图仍然是简单无向图)
思路
容易发现: 要使每一个节点的度为\(2\), 那么要形成若干个环(且没有多余边)
非常显然的是: 由于不可以出现重边或自环,所以环的节点数至少为3
继续观察发现: 最后的时候最多有两个环
因为节点数最大为\(8\), 而\(8<3+3+3\), 因此最多有两个环
Q:那么怎么记录所有的环的样子呢?
A:对于每一个环, 我们都可以将其看作为一个长度为环的节点数的数组:
如果数组为\(p_1,p_2,p_3,...,p_n\)
那么这个环就是: \(p_1\)连\(p_2\), \(p_2\)连\(p_3\),\(p_3\)连\(p_4\),...,\(p_{n-1}\)连\(p_n\), \(p_n\)连\(p_1\)
Q:那么怎么枚举呢?
A:全排列
没错,就是它,它可以枚举数组的所有的排列,每一个排列就表示一个环
就这样了?
Q:等一下! 两个环怎么办?
A:这个好办, 我们只要枚举第一个环的大小即可(从\(3\)枚举到 \(n-3\)), 然后把大数组分成两个小数组, 两个小数组代表了两个小的环
代码:
#include <iostream>
#include <algorithm>
#include <map>
#include <string.h>
using namespace std;
bool graph[9][9];
bool flag[9][9];
int a[29], b[29];
int q[9];
void f(int l, int r) // 构建出从q[l]到q[r]所对应的环。
{
for (int i = l; i < r; i++)
{
flag[q[i]][q[i + 1]] = true;
flag[q[i + 1]][q[i]] = true;
}
flag[q[r]][q[l]] = true;
flag[q[l]][q[r]] = true;
}
int f2(int n) // 比较两个邻接矩阵有多少个不同的位置,每一个不同的位置都需要将原边删除,或者增加一条边。
{
int cnt = 0;
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n; j++) // 注意j 从i + 1开始,因为不能将一条边多算一次。
{
cnt += (flag[i][j] != graph[i][j]);
}
}
return cnt;
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
cin >> a[i] >> b[i];
graph[a[i]][b[i]] = graph[b[i]][a[i]] = true; // 记下原图,使用邻接矩阵
}
for (int i = 1; i <= n; i++)
{
q[i] = i; // 设初始
}
int anss = 1e9;
do
{
// 一个环
memset(flag, 0, sizeof(flag));
f(1, n); // 构建环
int cnt1 = 0, cnt2 = 1e9;
cnt1 = f2(n); // 将原图改为一个环的代价。
// 两个环
for (int k = 3; k <= n - 3; k++) // 枚举第一个环的大小
{
memset(flag, 0, sizeof(flag));
int t = 0;
f(1, k); // 构建第一个环
f(k + 1, n); // 构建第二个环
t = f2(n);
cnt2 = min(cnt2, t); // 将原图改为两个环的代价。
}
anss = min(anss, min(cnt1, cnt2)); // 比较方案。
}
while (next_permutation(q + 1, q + n + 1)); // 改成下一排列。
cout << anss << endl;
return 0;
}

浙公网安备 33010602011771号