CF850D 做题感想
题目链接
关注 zyf ,顿顿解馋。
这道题目一看就一脸不可做的样子。。。
当然,如果你不知道竞赛图的性质,确实是非常不可做的。
竞赛图:任意两个点之间都有一条有向边连接,也就是把完全图的每一条边都赋予一个方向。
现在来看一下给你一个有关 \(n\) 个点的出度 \(deg_i\) 判断是否构成竞赛图。
兰道定理:对 \(deg_i\) 升序排好后, 若满足 \(\sum\limits_{i=1}^kdeg_i\geq C_k^2\) 且 \(\sum deg_i=C_n^2\) , 定能构造出一种竞赛图 。
现在考虑先构造出来一个序列 \(deg_i\) ,再由 \(deg_i\) 直接构造初始的竞赛图。
假设一共有 \(n\) 个点,容易发现 \(n\leq 61\) 。
因为有题目可以发现 \(m\leq 31\) ,然后可以列出方程:
接着我们用 \(F(i,j,k)\) 表示当前排到了第 \(i\) 个点,当前点度数为 \(a_j\) ,度数总和为 \(k\) 是否可行。
边界情况是 \(F(1,1,a_1)=1\) 证明显然。
转移也是非常简单 \(F(i,j,k)=\min\{1,F(i-1,j,k-a_j)+F(i-1,j-1-a_{j-1})\}\) 。
本人为了方便 \(\tt dp\) 采用的是顺推法求解 \(F(i,j,k)\) 。
然后枚举每一个 \(i\in[1,61]\) 找到 \(F(i,m,\frac{i\times(i-1)}{2})=1\) 的最小的 \(i\) 。
自此我们就找到了能够成竞赛图的 \(n\) 的最小值。
至于还原原来的序列,直接记录一下是由哪一个状态递推过来的,倒推就可以还原原序列。
现在的问题是:给定一个出度的序列 \(deg_i\) 怎么去构造一个竞赛图。
这个还是非常简单的了,每一次连边按照出度 \(deg_i\) 升序排序,然后直接向后连边即可。
像是这样:
bool cmp (int a, int b) {return deg[a] < deg[b];}
for (int i = 1; i <= n; i++) {
std::sort(pos + i, pos + 1 + n, cmp);
for (int j = i + 1; j <= i + deg[pos[i]]; j++) ok[pos[i]][pos[j]] = 1;
for (int j = i + deg[pos[i]] + 1; j <= n; j++) ok[pos[j]][pos[i]] = 1, deg[pos[j]] --;
}
所以细节的话可以直接看代码。
#include <set>
#include <map>
#include <cmath>
#include <queue>
#include <string>
#include <cstdio>
#include <cctype>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>
#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout)
#define quad putchar(' ')
#define Enter putchar('\n')
using std::abs;
using std::pair;
using std::string;
using std::make_pair;
// #define int long long
template <class T> void read(T &a);
template <class T> void write(T x);
template <class T, class ...rest> void read(T &a, rest &...x);
template <class T, class ...rest> void write(T x, rest ...a);
const int N = 105;
int m, a[N], limit[N], n, top, deg[N], tot, ok[N][N], pos[N];
int f[65][35][1905], sta[N], g[65][35][1905];
inline void Get_degree() {
int ii, jj, kk;
ii = n; jj = m; kk = n * (n - 1) / 2;
while (1) {
sta[++top] = a[jj];
if (g[ii][jj][kk] == 1) {
ii --; kk -= a[jj]; jj --;
} else if (g[ii][jj][kk] == 2){
ii --; kk -= a[jj];
} else break;
}
while (top) deg[++tot] = sta[top], top --;
return ;
}
bool cmp(int a, int b) {return deg[a] < deg[b]; }
signed main(void) {
read(m);
for (int i = 1; i <= m; i++) read(a[i]);
std::sort(a + 1, a + 1 + m);
for (int i = 1; i <= 80; i++) limit[i] = i * (i - 1) / 2;
f[1][1][a[1]] = 1;
for (int i = 1; i <= 61; i++) // 当前的点数
for (int j = 1; j <= m; j++) // 当前度数可以使用 a[1]~a[j]
for (int k = limit[i]; k <= i * a[j]; k++) // 当前的度数的总和
if (f[i][j][k] == 1) {
f[i + 1][j + 1][k + a[j + 1]] = f[i + 1][j][k + a[j]] = 1;
g[i + 1][j + 1][k + a[j + 1]] = 1;
g[i + 1][j][k + a[j]] = 2;
}
for (int i = 1; i <= 61; i++)
if (f[i][m][i * (i - 1) / 2] == 1) {n = i; break;}
if (n == 0) {std::cout << "=(" << std::endl; return 0;}
Get_degree();
// for (int i = 1; i <= n; i++) write(deg[i]), quad; Enter;
write(n); Enter;
for (int i = 1; i <= n; i++) pos[i] = i;
for (int i = 1; i <= n; i++) {
std::sort(pos + i, pos + 1 + n, cmp);
// for (int j = 1; j <= n; j++) write(deg[pos[j]]), quad; Enter;
for (int j = i + 1; j <= i + deg[pos[i]]; j++) ok[pos[i]][pos[j]] = 1;
for (int j = i + deg[pos[i]] + 1; j <= n; j++) ok[pos[j]][pos[i]] = 1, deg[pos[j]] --;
}
for (int i = 1; i <= n; i++, Enter)
for (int j = 1; j <= n; j++) write(ok[i][j]);
return 0;
}
template <class T> void read(T &a) {
int s = 0, t = 1;
char c = getchar();
while (!isdigit(c) && c != '-') c = getchar();
if (c == '-') c = getchar(), t = -1;
while (isdigit(c)) s = s * 10 + c - '0', c = getchar();
a = s * t;
}
template <class T> void write(T x) {
if (x == 0) putchar('0');
if (x < 0) putchar('-'), x = -x;
int top = 0, sta[50] = {0};
while (x) sta[++top] = x % 10, x /= 10;
while (top) putchar(sta[top] + '0'), top --;
return ;
}
template <class T, class ...rest> void read(T &a, rest &...x) {
read(a); read(x...);
}
template <class T, class ...rest> void write(T x, rest ...a) {
write(x); quad; write(a...);
}