Description
一个竞赛图的度数集合是由该竞赛图中每个点的出度所构成的集合。 现给定一个 $m$ 个元素的集合,第 $i$ 个元素是 $a_i$。判断其是否是一个竞赛图的度数集合,如果是,找到点数最小的满足条件的竞赛图。
$1\le m\le 31$,$0\le a_i\le 30$,$a_i$ 互不相同。
Solution
这题好难啊,参考了别人的题解&代码。
首先,题目中提到的兰道定理:将出度数组 $d$ 升序排序。$1≤i≤n$ 时,$\sum^i_{j=1}d_i\leq \dfrac{i(i−1)}{2}$。当且仅当 $i=n$ 时取等。
这可以用来判定竞赛图。
然后在本题中,套进去,可以得到 $n_{max}=61$。
然后考虑得到点数以及每个点的出度——跑一个完全背包。
$f(i,j,k)$ 表示集合中前 $j$ 个元素能否构成一个 $i$ 个点 $k$ 条边的竞赛图。
转移非常简单,$f(i,j,k)=f(i-1,j,k-a_j) \operatorname{or}f(i-1,j-1,k-a_{j-1})$。
实际转移用刷表更好写。
然后 DFS 回溯一下,可以得到每个点的出度。
接下来是如何构造竞赛图:
有一个比较贪心的思路:每次找一个剩余出度最少的点,向其他点依次连边,直到剩余出度为 $0$,接着让没被连边的点和它连一条边,最后删掉这个点。
正确性感性理解即可,还算直观。
Code
#include <algorithm>
#include <iostream>
#include <vector>
using std::cin;
using std::cout;
using std::endl;
const int maxn = 61, maxm = 1832;
int f[maxn + 3][maxn + 3][maxm + 3];
int pos[maxn + 3][maxn + 3][maxm + 3];
int n, a[maxn + 3], ans[maxn + 3];
void find_ans(int i, int j, int k)
{
if (i == 0)
return;
ans[i] = a[j], k -= a[j];
if (f[i - 1][j][k])
find_ans(i - 1, j, k);
else
find_ans(i - 1, j - 1, k);
}
bool cmp(int x, int y) { return ans[x] < ans[y]; }
int e[maxn + 3][maxn + 3];
int main()
{
std::ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
std::sort(a + 1, a + 1 + n);
f[1][1][a[1]] = 1;
for (int i = 1; i <= maxn; i++)
for (int j = 1; j <= n; j++)
for (int k = i * (i - 1) / 2; k <= maxm; k++)
if (f[i][j][k])
f[i + 1][j][k + a[j]] = f[i + 1][j + 1][k + a[j + 1]] = 1;
int res = 0;
for (int i = n; i <= maxn; i++)
if (f[i][n][i * (i - 1) / 2])
{
res = i;
break;
}
if (!res)
{
cout << "=)" << endl;
return 0;
}
cout << res << endl;
find_ans(res, n, res * (res - 1) / 2);
int pos[maxn];
for (int i = 1; i <= res; i++)
pos[i] = i;
for (int i = 1; i <= res; i++)
{
std::sort(pos + i, pos + 1 + res, cmp);
for (int j = i + 1; j <= i + ans[pos[i]]; j++)
e[pos[i]][pos[j]] = 1;
for (int j = i + ans[pos[i]] + 1; j <= res; j++)
e[pos[j]][pos[i]] = 1, ans[pos[j]]--;
}
for (int i = 1; i <= res; i++, cout << endl)
for (int j = 1; j <= res; j++)
cout << e[i][j];
return 0;
}