牛牛补题
牛牛补题
Bustling City
题意:
给定一个\(w_i\)表示 i 号城市每年会往 \(w_i\) 城市迁移一个人,对于每个点而言,求一个最小的年份满足至少有 m 个城市往当前点迁移,如果没有的话 输出 -1 .
思路:
这个题目首先一看就是一颗基环树,也就是 n 个点 n 个边,如果对于每个基环来进行操作的话,我们先处理出环上的点,对于环上每个点都处理出一个 f 表示当前点的最小的代价,然后围着这个环转两圈,为啥要转两圈捏,是因为第一圈可能会更新首尾,需要再来一圈用更新过首来更新其他的。对于不是环上的点,就可以用树上启发式合并,先处理重儿子并且记录下来。在用这个值来更新。
1--预处理出环
//找环
void dfs1(int u) {
de[u] = ++top;
stk[de[u]] = u;
int j = to[u];
if (!de[j])
dfs1(j);
else if (de[j] <= de[u]) {
int k = 1;
for (int i = top; i >= de[j]; i--) {
cir[++idx] = stk[i];
num[cir[idx]] = k++;
}
}
top--;
}
2--对于不是环上的点找出重儿子
void dfs2(int u) {
son[u] = 0, mx[u] = 1;
// cout << u << endl;
for (int j : v[u]) {
if (num[j]) continue;
//如果是环就G
dfs2(j);
if (mx[u] < mx[j] + 1) {
mx[u] = mx[j] + 1;
son[u] = j;
}
}
}
3--求答案
void dfs3(int u) {
ddfn[u] = ++dfn;// dfs序
// f表示当前序号能够到达的点的个数。
f[ddfn[u]] = 1;// 每个值当前的满足条件的就是自己。
if (son[u]) dfs3(son[u]), ans[u] = min(ans[u], ans[son[u]] + 1);
// 如果有重儿子就优先更新重儿子。
for (int j : v[u]) {
//枚举其他的儿子
if (j == son[u] || num[j]) continue;
dfs3(j);
for (int k2 = 1; k2 <= mx[j]; k2++) {
// 这里 就是最深的深度,找到这个点的最大深度,然后进行更新
//f 表示到 u 点 距离 为 k 的方案数
int tmp = (f[ddfn[u] + k2] += f[ddfn[j] + k2 - 1]);
// 如果大于m 就可以
if (tmp >= m) {
ans[u] = min(ans[u], k2);
}
}
}
}
Code
const int N = 2e6 + 100, mod = 1e9 + 7, INF = 1e10;
int lowbit(int x) { return x & -x; }
int gcd(int a, int b) { return a % b == 0 ? b : gcd(b, a % b); }
// 深度 环上的点 点的编号 dfs 序 栈
int de[N], cir[N], num[N], to[N], dfn, stk[N], top, idx;
int ddfn[N];
// 最大深度 son 下一个节点 f 是记录以 u 为根的答案 ans 是最后的答案
int mx[N], son[N], f[N], ans[N];
vector<int> v[N]; //存反向边
int n, m;
// w是
struct T {
int w, j, i, k;
bool operator<(const T& W) const { return w < W.w; }
} t[N], c2;
//找环
void dfs1(int u) {
de[u] = ++top;
stk[de[u]] = u;
int j = to[u];
if (!de[j])
dfs1(j);
else if (de[j] <= de[u]) {
int k = 1;
for (int i = top; i >= de[j]; i--) {
cir[++idx] = stk[i];
num[cir[idx]] = k++;
}
}
top--;
}
//对于不是环上的点找出重儿子
// 同时找出一个点最多往下面走多少步
void dfs2(int u) {
son[u] = 0, mx[u] = 1;
// cout << u << endl;
for (int j : v[u]) {
if (num[j]) continue;
//如果是环就G
dfs2(j);
if (mx[u] < mx[j] + 1) {
mx[u] = mx[j] + 1;
son[u] = j;
}
}
}
//求ans
void dfs3(int u) {
ddfn[u] = ++dfn;
f[ddfn[u]] = 1;
// 优先更新重儿子
if (son[u]) dfs3(son[u]), ans[u] = min(ans[u], ans[son[u]] + 1);
for (int j : v[u]) {
if (j == son[u] || num[j]) continue;
dfs3(j);
///最长的长度是知道的,然后根据这个来更新结果,为什么可以直接+,是因为我们保证了先处理重儿子,所以他们的序号是连续的,这个地方听秒的。
for (int k2 = 1; k2 <= mx[j]; k2++) {
int tmp = (f[ddfn[u] + k2] += f[ddfn[j] + k2 - 1]);
if (tmp >= m) {
ans[u] = min(ans[u], k2);
}
}
}
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> to[i];
v[to[i]].ps(i);//反向边,因为最后是讨论一个点有多少个点能够走到。
}
// 初始化
memset(de + 1, 0, n << 2);
memset(num + 1, 0, n << 2);
memset(ans + 1, 61, n << 2);
idx = dfn = 0;
for (int s = 1; s <= n; s++) {
if (ddfn[s]) continue;
// 如果没有走过。。
idx = 0;
dfs1(s);
int x = 0;
for (int i = 1; i <= idx; i++) {
int e = cir[i];
de[e] = 1;
dfs2(e);
dfs3(e);
// 这里是找出所有的点 对于每个点存下他的下标,他在到环上的点一号点的距离
// 它对应的dfs 序列,它对应的f值,距离要考虑他环上的位置,二号点和一号店的距离是1.
// 一路累加过来
for (int j2 = 1; j2 <= mx[e]; j2++) {
t[++x] = {j2 - 1, (j2 + i - 2) % idx + 1, cir[i], f[ddfn[e] + j2 - 1]};
}
}
// 从距离小的开始更新相当于前缀和,保证正确性。
sort(t + 1, t + 1 + x);
memset(f + 1, 0, idx << 2);
for (int j2 = 1; j2 <= x; j2++) {
T v1 = t[j2];
int tm = (f[v1.j] += v1.k);
if (tm >= m) {
ans[v1.i] = min(ans[v1.i], v1.w);
}
}
//这里说过为啥要循环两次了。
for (int j = idx - 1; j >= 1; j--) {
ans[cir[j]] = min(ans[cir[j]], ans[cir[j + 1]] + 1);
}
ans[cir[idx]] = min(ans[cir[idx]], ans[cir[1]] + 1);
for (int j = idx - 1; j >= 1; j--) {
ans[cir[j]] = min(ans[cir[j]], ans[cir[j + 1]] + 1);
}
ans[cir[idx]] = min(ans[cir[idx]], ans[cir[1]] + 1);
}
for (int i = 1; i <= n; i++) {
if (ans[i] <= n) {
cout << ans[i] << " ";
} else
cout << -1 << " ";
}
}
signed main() {
kd;
int _;
_ = 1;
// cin>>_;
while (_--) solve();
return 0;
}
Boss
题意:
有 n 座城市,m 个人,每个人去到每个城市有个时间,每座城市有一个需要的人数,保证这个需要的人数之和等于m,问最小的时间花费。
思路:
n 很小,考虑记录每个人从 哪座城市到哪座城市的代价,然后先把所有的人都放到 1 号点先,对于 其他的人,就对于 1 号点开始更新,考虑对于每个城市而言,都要从 1 号点放 \(C_i\) 个人到 当前的城市,考虑用spfa进行转移,对于每个点都进行 \(C_i\)次转移。在转移的过程中用优先队列对这个过程进行优化,每次都取出最小值。
Code
const int N = 2e6 + 100, mod = 1e9 + 7, INF = 1e10;
int lowbit(int x) { return x & -x; }
int gcd(int a, int b) { return a % b == 0 ? b : gcd(b, a % b); }
int in[N];// 表示当前点在那个位置
int c[N][15];//表示第 i 个人到 j 号城市的代价
int cnt[N], st[15], d[15], fa[15];
// 每座城市的 数量, st 表示当前城市有没有走过,d 表示从当前是 i 号城市的距离,fa表示是从哪个点转移过来的,最后需要回溯这个转移的过程然后把这些点的边都更新一边,
int n, k;
priority_queue<pi, vector<pi>, greater<pi> > e[12][12];
// 优先队列
int spfa(int x) {
queue<int> q;
q.push(x);
// 初始化
for (int i = 1; i <= k; i++) {
d[i] = INF, fa[i] = 0, st[i] = 0;
}
d[x] = 0, st[x] = 1;
while (q.size()) {
int t = q.front();
q.pop();
st[t] = 0;
for (int i = 1; i < x; i++) {
if (!e[i][t].size()) continue;
int w = e[i][t].top().x;
if (d[i] > d[t] + w) {
d[i] = d[t] + w;
fa[i] = t;
if (!st[i]) {
st[i] = 1;
q.push(i);
}
}
}
}
vector<int> v;
int x1 = 1;
while (x1 != x) {
// 每个点的最小值,都记录一下,然后放到v里面
v.ps(e[x1][fa[x1]].top().y);
in[e[x1][fa[x1]].top().y] = fa[x1];
x1 = fa[x1];
// cout << x1 << endl;
}
// 最后把v 里面的边都操作一下,因为 v 里面的 都被转移了。
// 所以需要新加入一些边
for (auto tt : v) {
for (int i = 1; i <= k; i++)
if (in[tt] != i) {
e[in[tt]][i].push({c[tt][i] - c[tt][in[tt]], tt});
}
}
return d[1];
}
void solve() {
cin >> n >> k;
for (int i = 1; i <= k; i++) {
cin >> cnt[i];
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= k; j++) {
cin >> c[i][j];
}
}
// 都放到1
int res = 0;
for (int i = 1; i <= n; i++) {
res += c[i][1];
in[i] = 1;
}
// 处理一下 从1 到 其他点的距离
for (int i = 2; i <= k; i++) {
for (int j = 1; j <= n; j++) {
e[in[j]][i].push({c[j][i] - c[j][in[j]], j});
}
// 先把当前的每个点的 in[i] 到 j 的距离更新好。
int ct = cnt[i];
while (ct--) {
for (int k1 = 1; k1 <= i; k1++) {
for (int k2 = 1; k2 <= i; k2++) {
while (e[k2][k1].size() > 0 && in[e[k2][k1].top().y] != k2) {
// 如果当前点的 top.y这个点所在的城市 不等于 k2,说明这个点被更新过,那么说明他被更新过,要pop
// cout << e[k2][k1].size() << endl;
e[k2][k1].pop();
}
}
}
res += spfa(i);
}
}
cout << res << endl;
}
signed main() {
kd;
int _;
_ = 1;
// cin>>_;
while (_--) solve();
return 0;
}
Forest
题意:
给你 n 个点,每个点之间的距离是已知的,问有所有可能的最小生成森林的边权和。
思路:
首先不可能去枚举出每个最小生成森林的情况,所以换种角度去考虑每条边对于最终状态的影响,会分成三种情况, 1. 边权比当前大, 2. 边权比当前小但是没有经过 u v,(u v 是当前这条边的两个端点),3. 边权比当前小同时经过了u v。情况 1 就是 \(2^{x}\):x是大于他的数量,第二种和第三种情况考虑使用容斥,用所有的情况去减去经过了u v的所有情况。
Code
const int N = 18, M = 1 << N, mod = 998244353, INF = 1e10;
int lowbit(int x) { return x & -x; }
int gcd(int a, int b) { return a % b == 0 ? b : gcd(b, a % b); }
int d[N * N][M]; //表示前面 i个点 边集为 j 的方案数
int g[M]; // 表示 边集为 j 的方案数
int pw[N * N];// 二的幂
struct T {
int x, y, w;
bool operator<(const T& W) const { return w < W.w; }
} e[N * N];// kur
int add(int x, int y) { return x + y >= mod ? (x + y - mod) : x + y; }
void solve() {
int n;
cin >> n;
int ans = 0;
pw[0] = 1;//
int all = (1 << n) - 1;// 全集,后面需要用到
for (int i = 1; i <= 100; i++) pw[i] = pw[i - 1] * 2 % mod;
int m = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
int c;
cin >> c;
if (i < j && c) {
e[m++] = {i, j, c};
}
}
}
for (int i = 0; i < n; i++) d[0][1 << i] = 1;
// 每个点只有自己这个点的情况是合法的。
// g 中每种情况都是1,因为是乘法原理。
for (int i = 0; i < (1 << n); i++) {
g[i] = 1;
}
sort(e, e + m);
for (int i = 0; i < m; i++) {
// 枚举边集
int now = pw[i];// 容斥第一步
int u = e[i].x, v = e[i].y, w = e[i].w;
for (int S = 0; S < (1 << n); S++) {
// 如果 s 包含了 u 和 v
if (!(S >> u & 1) || !(S >> v & 1)) continue;
// 那么就需要减去 (小于的*其他的补集-->*补集就是找出所有的情况)
now = add(now, mod - d[i][S] * g[S ^ all] % mod);
}
//*pw[m - 1 - i] 是情况1 大于的情况
// *自己的 边权
// *now 就是容斥之后
ans = add(ans, now * w % mod * pw[m - 1 - i] % mod);
for (int S = 0; S < 1 << n; S++) {
d[i + 1][S] = d[i][S];
// 如果包含了u v
if (!(S >> u & 1) || !(S >> v & 1)) continue;
// g 包含这条边的两种情况,所以需要×2
g[S] = g[S] * 2 % mod;
// 同理
d[i + 1][S] = d[i + 1][S] * 2 % mod;
for (int T = (S - 1) & S; T > S - T; T = (T - 1) & S) {
if ((T >> u & 1) ^ (T >> v & 1)) {
// 这里 S的一个子集包含了 u或者v的一个,那么可能会从 补集转过来
// 比如 T是 12 S是123 .那么你之前的再多一个 3 就可以转移过来。
d[i + 1][S] = (d[i + 1][S] + d[i][T] * d[i][S ^ T] % mod) % mod;
}
}
}
}
cout << ans << endl;
}
signed main() {
kd;
int _;
_ = 1;
// cin>>_;
while (_--) solve();
return 0;
}
LTCS
题意:
给出两棵树,求出满足一下三个条件的最大子集大小
- 是一个单射,也就是唯一确定,每一个点只能被用到一次。
- 对应的权值相等。
- 对于每个根节点而言,只能从他们的子节点之间进行匹配
思路:
对于这个题目,如果固定两颗树的两个结点的话,把他们儿子的匹配的值作为边权,对于可以匹配的情况建边,然后进行一次km算法,找出最大的匹配之后然后计算权值就可以。
Code
//看了题解感觉很板子,但是km不会,也不懂怎么把这个诡异的题意转化成人话
const int N = 600, mod = 1e9 + 7, INF = 1e10;
int lowbit(int x) { return x & -x; }
int gcd(int a, int b) { return a % b == 0 ? b : gcd(b, a % b); }
int c1[N], c2[N];
// 点权
int d[N][N], w[N][N];
//每个以 i 和 j 为结点的匹配情况,
// w 表示 每个情况的边权,用于km的匹配
vector<int> v1[N], v2[N];
//边权
// km 究极板子传入边权和边,找出最大的匹配
int km(int n, int m) {
vector<int> u(n + 1), v(m + 1), p(m + 1), Fa(m + 1);
for (int i = 1; i <= n; i++) {
p[0] = i;
int a = 0;
vector<int> mn(m + 1, INF);
vector<bool> used(m + 1, false);
do {
used[a] = true;
int i0 = p[a], del = INF, b = 0;
for (int j = 1; j <= m; ++j)
if (!used[j]) {
int val = w[i0][j] - u[i0] - v[j];
if (val < mn[j]) mn[j] = val, Fa[j] = a;
if (mn[j] < del) del = mn[j], b = j;
}
for (int j = 0; j <= m; ++j)
if (used[j])
u[p[j]] += del, v[j] -= del;
else
mn[j] -= del;
a = b;
} while (p[a] != 0);
do {
int b = Fa[a];
p[a] = p[b];
a = b;
} while (a);
}
return -v[0];
}
// dfs2
// 当前的结点。当前的父节点,第二颗的结点,第二课的父节点
void dfs2(int u1, int pr1, int u2, int pr2) {
for (int j : v2[u2]) {
//如果可以传下去就传
if (j != pr2) dfs2(u1, pr1, j, u2);
}
for (int j : v1[u1]) {
// 用儿子更新答案
if (j != pr1) d[u1][u2] = max(d[u1][u2], d[j][u2]);
}
for (int j : v2[u2]) {
// 同理
if (j != pr2) d[u1][u2] = max(d[u1][u2], d[u1][j]);
}
if (c1[u1] == c2[u2]) {
int f = (v1[u1].size() <= v2[u2].size());
// f 是为了km算法中把点数多的放到右边
for (int k1 = 0; k1 < v1[u1].size(); k1++) {
for (int k2 = 0; k2 < v2[u2].size(); k2++) {
int j1 = v1[u1][k1], j2 = v2[u2][k2];
int v = 0;
if (j1 != pr1 && j2 != pr2) {
v = -d[j1][j2];
}
f ? (w[k1 + 1][k2 + 1] = v) : (w[k2 + 1][k1 + 1]) = v;
}
}
if (f) {
d[u1][u2] = max(d[u1][u2], 1 - km(v1[u1].size(), v2[u2].size()));
} else
d[u1][u2] = max(d[u1][u2], 1 - km(v2[u2].size(), v1[u1].size()));
}
}
//
void dfs(int u, int pr) {
for (int j : v1[u]) {
if (j == pr) continue;
dfs(j, u);
}
dfs2(u, pr, 1, 0);
}
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> c1[i];
}
for (int i = 1; i <= m; i++) {
cin >> c2[i];
}
for (int i = 1; i < n; i++) {
int a, b;
cin >> a >> b;
v1[a].ps(b);
v1[b].ps(a);
}
for (int i = 1; i < m; i++) {
int a, b;
cin >> a >> b;
v2[a].ps(b);
v2[b].ps(a);
}
dfs(1, 0);
cout << d[1][1] << endl;
}
signed main() {
kd;
int _;
_ = 1;
// cin>>_;
while (_--) solve();
return 0;
}