闲话 22.12.12

闲话

?怎么犇犇突然开了?
那无耻引流一波(((

22.12.11 ARC106 选做
22.12.10 [蓝桥杯 2021 国 A] 积木 题解
22.12.9 有标号荒漠计数
22.12.8 流题其一

阅读随笔

分块指北

流题续写

插件在这里

Flood Fill

奇妙题。

考虑我们将 \(A\) 的所有同色四联通块缩成一个点。然后每个点拆成两个节点,分别表示这个点翻转/未翻转。考虑抽象成最小割。和源点连接表示该点染成了白色,和汇点连接表示该点染成了黑色。连接的边是染成该颜色的情况下对答案的贡献。然后发现相邻的点肯定有一个保留原色,因此相邻的点染成原色对应节点彼此连边。

做完了。预处理很恶心。

code
int M, s, t, n, m, cnt, a[1003][1003], b[1003][1003], bel[1003][1003], typ[N], siz[N][2];
char ch[1003];
bool vis[1003][1003];
map<pii, bool> mp;

pii stk[N];
int top;
int dx[] = { -1, 1, 0, 0 }, dy[] = { 0, 0, -1, 1 };
void bfss(pii st) {
    queue<pii> que; que.push(st);
    top = 0;
    while (que.size()) {
        auto now = que.front(); que.pop();
        stk[++ top] = now;
        vis[now.first][now.second] = 1;
        rep(i,0,3) {
            int tx = now.first + dx[i], ty = now.second + dy[i];
            if (a[tx][ty] == a[st.first][st.second] and !vis[tx][ty] and 1 <= tx and tx <= n and 1 <= ty and ty <= m) {
                vis[tx][ty] = 1;
                que.emplace(tx, ty);
            }
        }
    }
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    rep(i,1,n) {
        cin >> ch + 1;
        rep(j,1,m) a[i][j] = ch[j] & 1;
    }
    rep(i,1,n) {
        cin >> ch + 1;
        rep(j,1,m) b[i][j] = ch[j] & 1;
    }

    rep(i,1,n) rep(j,1,m) if (!vis[i][j]) {
        bfss({i, j});
        M ++;
        typ[M] = a[i][j];
        rep(idk,1,top) {
            bel[stk[idk].first][stk[idk].second] = M;
            siz[M][0] += (a[stk[idk].first][stk[idk].second] != b[stk[idk].first][stk[idk].second]);
            siz[M][1] += (a[stk[idk].first][stk[idk].second] == b[stk[idk].first][stk[idk].second]);
        }
    }
    rep(i,1,M) adde(i, i + M, inf);
    cnt = M;
    M *= 2;
    s = ++ M, t = ++ M;
    rep(i,1,cnt) 
        if (!typ[i]) adde(s, i, siz[i][0]), adde(i + cnt, t, siz[i][1]);
        else adde(s, i, siz[i][1]), adde(i + cnt, t, siz[i][0]);
    rep(x,1,n) rep(y,1,m) rep(i,0,3) {
        int tx = x + dx[i], ty = y + dy[i];
        if (1 <= tx and tx <= n and 1 <= ty and ty <= m) {
            if (!a[x][y] and a[tx][ty] and a[x][y] != a[tx][ty] and !mp[ {bel[x][y], bel[tx][ty]} ]) {
                adde(bel[x][y], bel[tx][ty] + cnt, inf);
                mp[ {bel[x][y], bel[tx][ty]} ] = mp[ {bel[tx][ty], bel[x][y]} ] = 1;
            }
        }
    }

    cout << Dinic(s, t);
}



数字配对

首先可以发现肯定是质因子幂和奇偶性不同的数字可以配对。因此将数按质因子幂和奇偶性分组,就是个二分图。\((i,j)\)\(\inf\) 流量,\(c_i\times c_j\) 费用的边,每个点向自己侧的超级点连边 \(b_i\) 流量,\(0\) 费用的边。

然后是一个最大瓶颈费用流的求。
考察 \(\text{Dinic}\) 的过程。我们每次跑出最大费用的路径,求出这条路径的流量。如果原费用加当前流量对应费用没有掉下瓶颈就直接加入,反之加入流量直到再加入就掉下瓶颈。在 \(\text{Dinic}\) 过程中轻易求得。

code
#include <bits/stdc++.h>
#define int long long
using namespace std; using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
template <typename T> T rand(T l, T r) { return uniform_int_distribution<T>(l, r)(rnd); }
template <typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
template <typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define ufile(x) 
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define Aster(i, s) for (int i = head[s], v; i; i = e[i].next)
#define all(s) s.begin(), s.end()
#define eb emplace_back
#define pb pop_back
#define em emplace
const int N = 2e6 + 5;
const int inf = LLONG_MAX;
int M, s, t, n, m, a[N], b[N], c[N];
int cnt[N];

int head[2][N], mlc = 1;
struct ep {
    int to, next, flow, cost;
} e[N << 2];
void adde(int u, int v, int fl, int cs, bool __same = false) {
    e[++ mlc] = { v, head[0][u], fl, cs };
    head[0][u] = mlc;
    e[++ mlc] = { u, head[0][v], (__same ? fl : 0), -cs };
    head[0][v] = mlc;
}

int dis[N]; bool vis[N];
bool spfa(int s, int t) {
    rep(i,1,M) dis[i] = -inf, vis[i] = false, head[1][i] = head[0][i];
    queue<int> que;
    dis[s] = 0, vis[s] = 1, que.emplace(s);
    while (que.size()) {
        int u = que.front(); que.pop(); vis[u] = 0;
        for (int i = head[0][u], v, w; i; i = e[i].next) {
            v = e[i].to, w = e[i].cost;
            if (e[i].flow and dis[v] < dis[u] + w) {
                dis[v] = dis[u] + w;
                if (!vis[v]) que.emplace(v), vis[v] = 1;
            }
        }
    } return dis[t] != - inf;
}

int dfs(int u, int in, int t) {
    if (u == t or in <= 0) return in;
    vis[u] = 1;
    int out = 0;
    for (int i = head[1][u], v, w, res; i and in > 0; i = e[i].next) {
        head[1][u] = i, v = e[i].to, w = e[i].cost;
        if (!vis[v] and e[i].flow and dis[v] == dis[u] + w) {
            res = dfs(v, min(in, e[i].flow), t);
            if (res > 0) {
                e[i].flow -= res, e[i ^ 1].flow += res;
                in -= res, out += res;
                if (in <= 0) break;
            }
        }
    } vis[u] = 0;
    return out;
}

int min_cost;
int Dinic(int s, int t) {
    int ret = 0, fl;
    while (spfa(s, t)) {
        fl = dfs(s, inf, t);
        if (min_cost + fl * dis[t] >= 0) ret += fl, min_cost += fl * dis[t];
        else {
            ret += min(fl, min_cost / (-dis[t]));
            break;
        }
    } assert(min_cost >= 0);
    return ret;
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n; rep(i,1,n) cin >> a[i]; rep(j,1,n) cin >> b[j]; rep(k,1,n) cin >> c[k];
    M = n; s = ++ M, t = ++ M;
    rep(i,1,n) {
        int x = a[i];
        for (int j = 2; j * j <= x; ++ j) if (x % j == 0) {
            while (x % j == 0) ++ cnt[i], x /= j;
        } if (x > 1) ++ cnt[i];
        if (cnt[i] & 1) adde(s, i, b[i], 0);
        else adde(i, t, b[i], 0);
    } 
    rep(i,1,n) if (cnt[i] & 1) {
        rep(j,1,n) if (!(cnt[j] & 1)) {
            int x = a[i], y = a[j];
            if (x > y) swap(x, y);
            if (y % x == 0 and abs(cnt[i] - cnt[j]) == 1) adde(i, j, 1ll * b[i] * b[j], c[i] * c[j]); 
        }
    }
    cout << Dinic(s, t) << '\n';
}



切糕

“最小割离散变量模型”?不是很懂。

最小割。然后考虑维护光滑度。我们需要让 \(i,j\) 两轴上割掉的边相距不超过 \(D\),就可以用一条不会被割掉的边连接任意 \(i\) 上的 \(k\) 点和 \(j\) 上的 \(k + D\) 点,这样这两轴上就不能同时选择 \(< k\) 的一条边和 \(> k + D\) 的边了。

求解最小割即可。

code
const int N = 2e6 + 5;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int M, s, t, p, q, r, D, ans, id[41][41][43];
int dx[] = { -1, 1, 0, 0 }, dy[] = { 0, 0, -1, 1 };

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> p >> q >> r; rep(x,1,p) rep(y,1,q) rep(z,1,r + 1) id[x][y][z] = ++ M;
    s = ++ M, t = ++ M; 
    rep(x,1,p) rep(y,1,q) adde(s, id[x][y][1], inf), adde(id[x][y][r + 1], t, inf);
    cin >> D;
    int tx, ty;
    rep(z,1,r) rep(x,1,p) rep(y,1,q) cin >> tx, adde(id[x][y][z], id[x][y][z + 1], tx);
    rep(x,1,p) rep(y,1,q) {
        rep(i,0,3) {
            tx = x + dx[i], ty = y + dy[i]; 
            if (tx < 1 or p < tx or ty < 1 or q < ty) continue;
            rep(z,D,r) adde(id[x][y][z], id[tx][ty][z - D], inf);
        }
    } 
    cout << Dinic(s, t);
}



美食节

有教育意义的题。可以先做一下 修车 这题。

对菜建点,对厨师拆点。菜的话从源点连流量为需求量、费用为 \(0\) 的边,每个厨师拆成倒数第 \(k\) 道菜的厨师。
那这是个二分图嘛,连边吧。第 \(i\) 个厨师倒数第 \(j\) 道菜是 \(k\),则菜向对应位置的厨师连边费用为 \(j \times a(k, i)\)、流量为 \(1\) 的边。直接跑?你在想啥呢?

我们只加入必要的边,记录每个厨师目前已经分配到几道菜了。假设有一位厨师已经被分配到 \(i\) 道菜,那他对应的连边是倒数第 \(i+1\) 道菜至倒数第 \(1\) 道菜的连边。每次跑增广后给被增广到的节点/被分配到菜的厨师增加一条连边。

复杂度是信仰。

code
#include <bits/stdc++.h>
#define int long long
using namespace std; using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
template <typename T> T rand(T l, T r) { return uniform_int_distribution<T>(l, r)(rnd); }
template <typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
template <typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define ufile(x) 
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define Aster(i, s) for (int i = head[s], v; i; i = e[i].next)
#define all(s) s.begin(), s.end()
#define eb emplace_back
#define pb pop_back
#define em emplace
const int N = 2e6 + 5;
const int inf = 0x3f3f3f3f3f3f3f3fll;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int M, s, t, n, m, t1, t2, t3, t4, p[45], now[105], nxt[N], sp, T[45][105];
#define id(i, j) ( ((i) - 1) * sp + (j) + n )

int head[2][N], mlc = 1;
struct ep {
    int to, next, flow, cost;
} e[N << 2];
void adde(int u, int v, int fl, int cs, bool __same = false) {
    e[++ mlc] = { v, head[0][u], fl, cs };
    head[0][u] = mlc;
    e[++ mlc] = { u, head[0][v], (__same ? fl : 0), -cs };
    head[0][v] = mlc;
}

int dis[N]; bool vis[N];
bool spfa(int s, int t) {
    rep(i,1,M) dis[i] = inf, vis[i] = false, head[1][i] = head[0][i];
    queue<int> que;
    dis[s] = 0, vis[s] = 1, que.emplace(s);
    while (que.size()) {
        int u = que.front(); que.pop(); vis[u] = 0;
        for (int i = head[0][u], v, w; i; i = e[i].next) {
            v = e[i].to, w = e[i].cost;
            if (e[i].flow and dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                if (!vis[v]) que.emplace(v), vis[v] = 1;
            }
        }
    } return dis[t] != inf;
}

int min_cost;
int dfs(int u, int in, int t) {
    if (u == t or in <= 0) return in;
    vis[u] = 1;
    int out = 0;
    for (int i = head[1][u], v, w, res; i and in > 0; i = e[i].next) {
        head[1][u] = i, v = e[i].to, w = e[i].cost;
        if (!vis[v] and e[i].flow and dis[v] == dis[u] + w) {
            res = dfs(v, min(in, e[i].flow), t);
            if (res > 0) {
                e[i].flow -= res, e[i ^ 1].flow += res;
                in -= res, out += res;
                min_cost += res * w;
                nxt[u] = v;
                if (in <= 0) break;
            }
        }
    } vis[u] = 0;
    return out;
}

int Dinic(int s, int t) {
    int ret = 0, fl;
    while (spfa(s, t)) {
        ret += dfs(s, inf, t);
        rep(j,1,m) if (nxt[id(j, now[j])] and now[j] < sp) {
            ++ now[j];
            rep(i,1,n) adde(i, id(j, now[j]), 1, T[i][j] * now[j]);
            adde(id(j, now[j]), t, 1, 0);
        }
    } return ret;
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    rep(i,1,n) cin >> p[i], sp += p[i]; 
    rep(i,1,n) rep(j,1,m) cin >> T[i][j];
    M = m * sp + n; s = ++ M, t = ++ M;
    rep(i,1,n) adde(s, i, p[i], 0);
    rep(i,1,n) rep(j,1,m) adde(i, id(j, 1), 1, T[i][j]);
    rep(i,1,m) {
        ++ now[i] = 1;
        adde(id(i, 1), t, 1, 0);
    } 
    Dinic(s, t);
    cout << min_cost << '\n';
}



最小割

[ZJOI2011]。首先这题是 Gomory-Hu Tree 板子题。其次我不会证 GH Tree 的正确性,我就是让你看看,这题挺好的。

怎么构造 GH Tree 呢?我们考虑一个最小割重构树。我们随便选两个点 \(s,t\) 跑最小割,最大流就是 \(s,t\) 两点间边的权值。由于最小割,图肯定被分成了至少两部分,我们分治解决 \(s\) 所在的联通块和剩余部分。最多分治 \(O(n)\) 层,我们做 \(O(n)\)\(\text{Dinic}\) 即可建出树。
\(u \to v\) 的最大流一定大于等于 \(u\to k\) 的最大流和 \(v\to k\) 的最大流中的较小值。这样对于一条最小割树上的路径,两端点的最小割一定大于等于路径边权的最小值。而这路径上的每个割都是两端点的一个割,因此这其中的最小值一定能够成为两端点的最小割。因此 GH Tree 是正确的。

感性理解哈(

\(\text{Bonus : }\) 如何求最小割必经边和可行边

code
int T, M, s, t, n, m, t1, t2, t3, id[155], ans[155][155];
int tmp[1000005], cnt;

int f[N];
vp g[N];
void cal(int u, int fa, int id) {
    for (auto [v, w] : g[u]) if (v != fa) {
        ans[id][v] = min(ans[id][u], w);
        cal(v, u, id);
    }
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> T;
    while (T--) {
        memset(ans, 0x3f, sizeof ans);
        cin >> n >> m; M = n, mlc = 1, cnt = 0;
        rep(i,1,M) head[0][i] = 0;
        rep(i,1,m) cin >> t1 >> t2 >> t3, adde(t1, t2, t3, true);
        rep(i,0,n) id[i] = i, g[i].clear();
        
        memset(f, 0, sizeof f);
        f[0] = -1;
        rep(i,1,n) {
            for (int j = 2; j <= mlc; j += 2) 
                e[j].flow = e[j ^ 1].flow = (e[j].flow + e[j ^ 1].flow) >> 1;
            int k = Dinic(f[i], i);
            g[i].eb(f[i], k);
            g[f[i]].eb(i, k);
            rep(j, i + 1, n) if (dep[j] == 0 and f[i] == f[j]) f[j] = i;
        }


        rep(i,0,n) cal(i, -1, i);

        rep(i,1,n) rep(j,i+1,n) tmp[++cnt] = ans[i][j];
        sort(tmp + 1, tmp + 1 + cnt);
        cin >> m;
        while (m --) {
            cin >> n;
            cout << upper_bound(tmp + 1, tmp + 1 + cnt, n) - tmp - 1 << '\n';
        }
        cout << '\n';
    }
}
posted @ 2022-12-12 21:17  joke3579  阅读(70)  评论(3编辑  收藏  举报