【题解】CF1556H DIY Tree
题目链接:CF1556H DIY Tree
题意:
给定 $n$ 个点,$\binom{n}{2}$ 条带权边,前 $k$ 个点是特殊点,每个特殊点 $i$ 有一个度数限制 $d_i$,求一棵边权和最小的生成树,使得该生成树中每个特殊点 $i$ 的度数 $\le d_i$。
$2\le n\le 50, 1\le k\le \min(n-1, 5)$。
拟阵交。
对于该无向图 $G=(V,E)$,令 $M_1=(E,\mathcal{I}_1),\ \mathcal{I}_1=\{F\subseteq E:F\text{ \small 无环}\}$。则 $M_1$ 是经典的图拟阵,$\text{kruskal}$ 算法就是基于图拟阵的。
如果 “加入一些边使得每个特殊点度数满足限制” 也是拟阵,就可以令 $M_2=(E,\mathcal{I}_2),\ \mathcal{I}_2=\{F\subseteq E : F\text{ \small 使得每个特殊点 } i \text{ 的度数} \le d_i \}$。
然后求
$$ \max_{I\in \mathcal{I}_1\cap \mathcal{I}_2} \omega(I) $$
其中 $\omega(I)=\sum_{e\in I} \omega(e)$。这样,问题就解决了。
但是实际上 “加入一些边使得每个特殊点度数满足限制” 不是拟阵。他满足遗传性,但是不满足交换性。也就是对于两组 $I,J\in \mathcal{I}_2$,且 $|J|>|I|$,不一定存在 $z\in J \backslash I$,使得 $I\cup z\in\mathcal{I}_2$。
这是因为有一些边只连接了一个关键点,他们对所有特殊点的度数贡献总和是 $1$,但连接了两个关键点的边对其贡献为 $2$。
因此,考虑将所有连接了两个特殊点的边拿出来,特殊处理。由于 $k\le 5$,所以这种边至多只有 $10$ 条,我们可以 $2^{10}$ 枚举他们是否出现,并检查是否合法。
这样,剩下的边就满足交换性了,接下来就对每一种特殊点之间的边的枚举方案,都做一遍拟阵交即可。
//Code By CXY07
#include<bits/stdc++.h>
using namespace std;
//#define FILE
//#define int long long
#define file(FILENAME) freopen(FILENAME".in", "r", stdin), freopen(FILENAME".out", "w", stdout)
#define randint(l, r) (rand() % ((r) - (l) + 1) + (l))
#define LINE() cout << "LINE = " << __LINE__ << endl
#define debug(x) cout << #x << " = " << x << endl
#define abs(x) ((x) < 0 ? (-(x)) : (x))
#define inv(x) qpow((x), mod - 2)
#define lowbit(x) ((x) & (-(x)))
#define ull unsigned long long
#define pii pair<int, int>
#define LL long long
#define mp make_pair
#define pb push_back
#define scd second
#define vec vector
#define fst first
#define endl '\n'
const int MAXN = 3010;
const int INF = 2e9;
const double eps = 1e-6;
const double PI = acos(-1);
//const int mod = 1e9 + 7;
//const int mod = 998244353;
//const int G = 3;
//const int base = 131;
int n, k, m1, m, Ans = INF;
int d_lim[MAXN], X[MAXN], Y[MAXN], W[MAXN];
bool vis[MAXN];
vec<int> G[MAXN];
template<typename T> inline bool read(T &a) {
a = 0; char c = getchar(); int f = 1;
while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar(); }
while(c >= '0' && c <= '9') { a = a * 10 + (c ^ 48); c = getchar(); }
return a *= f, true;
}
struct M1 {
int S;
struct DSU {
int f[MAXN];
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
void unity(int x, int y) { (find(x) != find(y)) && (f[find(x)] = find(y)); }
bool Init(int S) {
for(int i = 1; i <= n; ++i) f[i] = i;
for(int i = 1; i <= m1; ++i) {
if(!((S >> (i - 1)) & 1)) continue;
if(find(X[i]) != find(Y[i])) unity(X[i], Y[i]);
else return false;
}
return true;
}
} ex[MAXN], tmp;
void Init(int _S) { S = _S; }
bool chk(int S) { return tmp.Init(S); }
void build() {
for(int i = m1 + 1; i <= m + 1; ++i) {
if(!vis[i] && i <= m) continue;
ex[i].Init(S);
for(int j = m1 + 1; j <= m; ++j) {
if(!vis[j] || i == j) continue;
ex[i].unity(X[j], Y[j]);
}
}
}
bool valid(int x) { return ex[m + 1].find(X[x]) != ex[m + 1].find(Y[x]); }
bool valid(int x, int y) { return (ex[x].find(X[y]) != ex[x].find(Y[y])); }
} M1;
struct M2 {
int S, d[MAXN];
void add(int S) {
for(int i = 1; i <= n; ++i) d[i] = 0;
for(int i = 1; i <= m1; ++i) if((S >> (i - 1)) & 1) d[X[i]]++, d[Y[i]]++;
}
bool chk(int S) {
add(S);
for(int i = 1; i <= k; ++i) if(d[i] > d_lim[i]) return false;
return true;
}
void Init(int _S) { S = _S; }
void build() {
add(S);
for(int i = m1 + 1; i <= m; ++i)
if(vis[i]) d[X[i]]++, d[Y[i]]++;
}
bool valid(int x) {
d[X[x]]++, d[Y[x]]++;
bool res = (d[X[x]] <= d_lim[X[x]] && d[Y[x]] <= d_lim[Y[x]]);
d[X[x]]--, d[Y[x]]--; return res;
}
bool valid(int x, int y) {
d[X[x]]--, d[Y[x]]--, d[X[y]]++, d[Y[y]]++;
bool res = (d[X[y]] <= d_lim[X[y]] && d[Y[y]] <= d_lim[Y[y]]);
d[X[x]]++, d[Y[x]]++, d[X[y]]--, d[Y[y]]--; return res;
}
} M2;
bool SPFA(int _s, int _t) {
queue<int> Q;
static int dis[MAXN], len[MAXN], pre[MAXN];
static bool inq[MAXN];
while(Q.size()) Q.pop();
for(int i = m1 + 1; i <= m + 2; ++i)
dis[i] = INF, len[i] = pre[i] = 0, inq[i] = false;
dis[_s] = 0, inq[_s] = true; Q.push(_s);
while(Q.size()) {
int x = Q.front(); Q.pop(); inq[x] = false;
for(auto to : G[x]) {
int w = vis[to] ? -W[to] : W[to];
if(dis[to] > dis[x] + w || (dis[to] == dis[x] + w && len[to] > len[x] + 1)) {
dis[to] = dis[x] + w, len[to] = len[x] + 1, pre[to] = x;
if(!inq[to]) Q.push(to), inq[to] = true;
}
}
}
if(dis[_t] == INF) return false;
for(int x = pre[_t]; x != _s; x = pre[x]) vis[x] ^= 1;
return true;
}
bool Augment() {
M1.build(); M2.build(); int _s = m + 1, _t = m + 2;
for(int i = m1 + 1; i <= m + 2; ++i) G[i].clear();
for(int i = m1 + 1; i <= m; ++i) {
if(vis[i]) continue;
if(M1.valid(i)) G[_s].pb(i);
if(M2.valid(i)) G[i].pb(_t);
}
for(int i = m1 + 1; i <= m; ++i) {
if(!vis[i]) continue;
for(int j = m1 + 1; j <= m; ++j) {
if(vis[j]) continue;
if(M1.valid(i, j)) G[i].pb(j);
if(M2.valid(i, j)) G[j].pb(i);
}
}
return SPFA(_s, _t);
}
int Matroid_Intersection(int S) {
int res = 0, cnt = n - 1 - __builtin_popcount(S);
memset(vis, false, sizeof vis);
while(Augment()) cnt--;
if(cnt) return INF;
for(int i = 0; i < m1; ++i) if((S >> i) & 1) res += W[i + 1];
for(int i = m1 + 1; i <= m; ++i) if(vis[i]) res += W[i];
return res;
}
signed main () {
#ifdef FILE
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
#endif
read(n), read(k);
for(int i = 1; i <= k; ++i) read(d_lim[i]);
for(int i = k + 1; i <= n; ++i) d_lim[i] = INF;
m = (k - 1) * k >> 1;
for(int i = 1; i <= n; ++i)
for(int j = i + 1; j <= n; ++j) {
if(j > k) { read(W[++m]); X[m] = i, Y[m] = j; }
else { read(W[++m1]); X[m1] = i, Y[m1] = j; }
}
for(int S = 0; S < (1 << m1); ++S) {
if(!M1.chk(S) || !M2.chk(S)) continue;
M1.Init(S); M2.Init(S);
Ans = min(Ans, Matroid_Intersection(S));
}
printf("%d\n", Ans);
return 0;
}