「2019 JizhongトレーニングDay12」問題解決レポート

T1、迷路

$ n個の\回数M(n個の\当量5、m個の\当量10 ^ 5)の$ マトリックス\(0 \)行くことができない格子を表し、\(1 \)は、格子は、右、下、だけ上がることができ表します三の方向に行きます。Q(Qの\の当量5 \が$ 10倍^ 4)$ 操作は、二つの動作がある:
1、格子の変性タイプは、
2、$からクエリ(a、b)は$に達したかどうかを\((Cを、D)\) ()後者を確実にするために、前者の右側に。

\(ソル\)

観測された\(N- \のLeq 5 \) それは非常に重要なヒントです。
セグメントツリーは、横方向に内蔵され、列の格子列があるべきであるマトリックスで独自にそれを取ることができない場合、特に、カラム上の通信ポイントとの間の関係を記述する行列として各セグメントツリーのリーフノード到達不能として表さ、
列挙合成中間点\(N ^ 3 \)合成暴力。
2つの点が変更書かれ、その後消えて変更することがあり、このコラム次のコラム、共感の合併に記述されている各行列のリーフノードの接続関係:しかし、私は診察室に大型練習定数を書きました。

\(出典\)

#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}

const int N = 2e5 + 5;

int n, m, q, mp[7][N];

template<typename T>inline void chk_min(T &_, T __) {
    _ = !~_ ? __ : (_ < __ ? _ : __);
}
struct node {
    int a[6][6];
    node () {
        memset(a, -1, sizeof(a));
    }
    inline int* operator [] (const int x) {
        return a[x];
    }
    inline node operator + (node b) const {
        node ret;
        for (int k = 1; k <= n; ++k)
            for (int i = 1; i <= n; ++i)
                if (~a[i][k])
                    for (int j = 1; j <= n; ++j)
                        if (~b[k][j])
                            chk_min(ret[i][j], a[i][k] + b[k][j] + 1);
        return ret;
    }
} ;
struct segment_tree {
    node t[N << 2];
    void init(int x, int p) {
        for (int i = 1, j; i <= n; ++i) {
            for (j = i; j <= n && mp[j][x]; ++j)
                t[p][i][j] = j - i;
            for (; j <= n; ++j)
                t[p][i][j] = -1;
            for (j = i; j && mp[j][x]; --j)
                t[p][i][j] = i - j;
            for (; j; --j)
                t[p][i][j] = -1;
        }
    }
    inline void push_up(int p) {
        t[p] = t[p << 1] + t[p << 1 | 1];
    }
    void build(int tl, int tr, int p) {
        if (tl == tr)
            return init(tl, p);
        int mid = (tl + tr) >> 1;
        build(tl, mid, p << 1), build(mid + 1, tr, p << 1 | 1);
        push_up(p);
    }
    void modify(int pos, int tl, int tr, int p) {
        if (tl == tr)
            return init(tl, p);
        int mid = (tl + tr) >> 1;
        if (mid >= pos)
            modify(pos, tl, mid, p << 1);
        else
            modify(pos, mid + 1, tr, p << 1 | 1);
        push_up(p);
    }
    node query(int l, int r, int tl, int tr, int p) {
        if (l <= tl && tr <= r)
            return t[p];
        int mid = (tl + tr) >> 1;
        if (mid < l)
            return query(l, r, mid + 1, tr, p << 1 | 1);
        if (mid >= r)
            return query(l, r, tl, mid, p << 1);
        return query(l, r, tl, mid, p << 1) +
               query(l, r, mid + 1, tr, p << 1 | 1);
    }
} T;

int main() {
    //freopen("in", "r", stdin);
    freopen("maze.in", "r", stdin);
    freopen("maze.out", "w", stdout);
    n = in(), m = in(), q = in();
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            mp[i][j] = in();
    T.build(1, m, 1);

    int a, b, c, d;
    node res;
    while (q--) {
        if (in() == 1) {
            a = in(), b = in();
            mp[a][b] ^= 1;
            T.modify(b, 1, m, 1);
        } else {
            a = in(), b = in(), c = in(), d = in();
            res = T.query(b, d, 1, m, 1);
            printf("%d\n", res[a][c]);
        }
    }
    return 0;
}

T2、漢王蒙

そこ平面\(N \(N \の当量 10 ^ 5)\) 黒ドット、\(N- \のLeq 10 ^ Mの\(5)\)白点、黒点\(X \)と白色点\(Y \)以下のマンハッタン距離である\(D \(D \のLeq 10 ^ 9)\)次いで\(X \)する(Y \)を\エッジ、または接続されている\(Y \)をする(Xを\ \)でもエッジ。
不確実性の色点との間にも同じ方向、少なくとも最小および最大の黒ドットと白ドット3員環を見つけます。

\(ソル\)

明らかに、3員環上の同じ色の2つの点があります。
でもライス側は、我々は気にしない方法ですので、唯一のリングのエッジなので、同じ黒と白のドット共感するかどうかを気に。
\は(カバー(x)は\)を表し\(X \)の白色点をカバーすることができ、\(カバー(X、Y)を\)を表し\(X、Yの\)を一緒にホワイトポイントをカバーします。
以下の実施例の最大値(最小共感)。

\ [\開始{整列}回答=&\のsum_ {X、SのY \} \最大\ {カバー(X) - カバー{(X、Y)}、カバー(Y) - カバー(x、y)を\ } \\ =&\のsum_ {X、SのY \} \最大\ {カバー(x)は、カバー(Y)\} - \ sum_ {X、SのY \}カバー(x、y)は\\ = &\ sum_ {SにおけるX、Yの\} \最大\ {カバー(x)は、カバー(Y)\} - \ sum_ {X \ Sにおける} \ binom {カバー(X)} {2} \\ \端{ALIGN} \]

限り計算した\(カバーは、(X)\) ;とすることができる
走査線が行には、次のように維持します。

\(出典\)

#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 1e5 + 5;
struct node {
    int x, y;
} a[N], b[N];
struct options {
    long long x; int y, z, id;
    int typ;
} opt[N * 3];

int n, m, d, nn, cntx[N], cnty[N];
long long ans_min, ans_max;

long long tmp[N * 3];
inline bool cmpx(const options &i, const options &j) {
    if (i.x == j.x)
        return i.typ == 0 ? 1 : j.typ != 0;
    return i.x < j.x;
}
void prep() {
    nn = 0;
    for (int i = 1; i <= m; ++i)
        tmp[i] = b[i].y;
    for (int i = 1; i <= n; ++i)
        tmp[i + m] = 1ll * a[i].y - d;
    for (int i = 1; i <= n; ++i)
        tmp[i + n + m] = 1ll * a[i].y + d;
    std::sort(tmp + 1, tmp + 1 + n + n + m);
    nn = std::unique(tmp + 1, tmp + 1 + n + n + m) - tmp - 1;
    for (int i = 1; i <= m; ++i) {
        opt[i].typ = opt[i].id = 0;
        opt[i].x = b[i].x;
        opt[i].y = std::lower_bound(tmp + 1, tmp + 1 + nn, b[i].y) - tmp;
        opt[i].z = 0;
    }
    for (int i = 1; i <= n; ++i) {
        opt[i + m].typ = -1;
        opt[i + m].id = i;
        opt[i + m].x = 1ll * a[i].x - d - 1;

        opt[i + n + m].typ = 1;
        opt[i + n + m].id = i;
        opt[i + n + m].x = 1ll * a[i].x + d;
        
        opt[i + m].y = opt[i + n + m].y = std::lower_bound(tmp + 1, tmp + 1 + nn, 1ll * a[i].y - d) - tmp;
        opt[i + m].z = opt[i + n + m].z = std::lower_bound(tmp + 1, tmp + 1 + nn, 1ll * a[i].y + d) - tmp;
    }
    std::sort(opt + 1, opt + 1 + n + n + m, cmpx);
}

struct binary_index_tree {
    int a[N * 3];
    inline void init() { memset(a, 0, sizeof(a)); }
    void insert(int p, int k) { for (; p <= nn; p += (p & -p)) a[p] += k; }
    int ask(int p, int ret = 0) { for (; p; p -= (p & -p)) ret += a[p]; return ret; }
} bit;

void work() {
    prep();
    bit.init();
    for (int i = 1; i <= n + n + m; ++i) {
        if (!opt[i].typ) {
            bit.insert(opt[i].y, 1);
        } else {
            cntx[opt[i].id] += opt[i].typ * (bit.ask(opt[i].z) - bit.ask(opt[i].y - 1));
        }
    }
}

int main() {
    //freopen("in", "r", stdin);
    freopen("mhw.in", "r", stdin);
    freopen("mhw.out", "w", stdout);
    n = in(), m = in(), d = in();
    for (int i = 1; i <= n; ++i) {
        a[i] = (node){in(), in()};
        a[i] = (node){a[i].x + a[i].y, a[i].x - a[i].y};
    }
    for (int i = 1; i <= m; ++i) {
        b[i] = (node){in(), in()};
        b[i] = (node){b[i].x + b[i].y, b[i].x - b[i].y};
    }
    work();
    std::swap(a, b);
    std::swap(n, m);
    std::swap(cntx, cnty);
    work();

    std::sort(cntx + 1, cntx + 1 + n);
    std::sort(cnty + 1, cnty + 1 + m);
    for (int i = 1; i <= n; ++i) {
        ans_min += 1ll * cntx[i] * (n - i);
        ans_min -= 1ll * cntx[i] * (cntx[i] - 1) / 2;
        ans_max += 1ll * cntx[i] * (i - 1);
        ans_max -= 1ll * cntx[i] * (cntx[i] - 1) / 2;
    }
    for (int i = 1; i <= m; ++i) {
        ans_min += 1ll * cnty[i] * (m - i);
        ans_min -= 1ll * cnty[i] * (cnty[i] - 1) / 2;
        ans_max += 1ll * cnty[i] * (i - 1);
        ans_max -= 1ll * cnty[i] * (cnty[i] - 1) / 2;
    }
    printf("%lld %lld\n", ans_min, ans_max);
    return 0;
}

T1、工場

すべての極大マッチング部グラフが完全に一致しているように、二部グラフを考えるとは縁取りすることができます。

\(ソル\)

一つのことを証明するために:ユニコムポイントとエッジがさえ満ちている場合にのみ、各ブロックでこの二部グラフについて同じ場合は\((A)\) 質問の意味を満たすために\((B)\) \(B \)する(\)を\明らかであろ、省略する。\(\)する(B \)を\の矛盾によるものであってもよい:2点が反対側ユニコムブロックがある場合(\ \ )、\ (B \)が見つかり縁部との間に接続されていない\(\)する(B \)\経路を、辺の集合に一致する辺の奇数が見つかり、この偉大に基づいて一致を見つけるために、添加しました完全一致でなければならない。とらパスに設定され、奇数側一致縁、辺の偶数を添加するが、今回は最大の一致は完全一致ではありません。\(QED \)検討ユニコム、いくつかのブロックは、次のようにマージされます\((\ SUMのX_I)^ 2 \) \(F_ {S、i}は \) 現在のブロックが選択されたリンクとして設定されていることを示している\(S \) 及び点に配置されている\(2I \) これらは、上記の基準を満たすように)ユニコムブロックを最小のコスト。二つの方法を転送します。







\ [\開始{整列} F_ {S \ bigcup {X}、I} =&\分(F_ {S \ bigcup {X}、I}、F_ {S、I})\\ F_ {S、I} =&\ MIN_ {J = 0 } ^ {I - 1} \ {F_ {S、J} +(I - J)^ 2 \} \、(\ Sigma_ {X \ s内} = \ Sigma_ {Y \ \端{整列)} Sで
\]} すぎるすぎる状態の数ので、圧力のような方法を最適化することができないので、
我々は懸念しているだけユニコム各ブロック(左右分類点)数があり、変更することができます状態の数を最適化するために、小数、
話題と人々は私たちに最も言われています\(172032 \)の状態を。

\(出典\)

#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 35, inf = 0x3f3f3f3f;

int n, m, mp[N << 1][N << 1], tmp_x, tmp_y, init_edge, buc[N][N], pre[N << 1], f[172033][N], num[N << 1];
bool vis[N << 1];

struct node {
    int x, y;
    inline bool operator < (const node &b) const {
        return buc[this->x][this->y] < buc[b.x][b.y];
    }
    inline bool operator == (const node &b) const {
        return this->x == b.x && this->y == b.y;
    }
} a[N + N];

void prep(const int u) {
    vis[u] = 1;
    if (u <= n)
        ++tmp_x;
    else
        ++tmp_y;
    for (int i = 1; i <= mp[u][0]; ++i)
        if (!vis[mp[u][i]])
            prep(mp[u][i]);
}

int main() {
    //freopen("in", "r", stdin);
    freopen("factory.in", "r", stdin);
    freopen("factory.out", "w", stdout);
    n = in();
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j) {
            char c = getchar();
            while (c < '0' || c > '1')
                c = getchar();
            if (c == '1') {
                mp[i][++mp[i][0]] = j + n;
                mp[j + n][++mp[j + n][0]] = i;
            }
            init_edge += c == '1';
        }
    for (int i = 1; i <= n + n; ++i)
        if (!vis[i]) {
            tmp_x = tmp_y = 0;
            prep(i);
            ++buc[tmp_x][tmp_y];
        }

    for (int i = 0; i <= n; ++i)
        for (int j = 0; j <= n; ++j)
            if (buc[i][j])
                a[++m] = (node){i, j};
    std::sort(a + 1, a + 1 + m);
    m = std::unique(a + 1, a + 1 + m) - a - 1;
    pre[1] = 1;
    for (int i = 2; i <= m + 1; ++i)
        pre[i] = pre[i - 1] * (buc[a[i - 1].x][a[i - 1].y] + 1);

    memset(f, inf, sizeof(f));
    f[0][0] = 0;

    int nowx, nowy;
    for (int s = 0; s < (pre[m + 1]); ++s) {
        nowx = nowy = 0;
        for (int i = 1; i <= m; ++i)
            num[i] = s % pre[i + 1] / pre[i];
        for (int i = 1; i <= m; ++i)
            nowx += num[i] * a[i].x, nowy += num[i] * a[i].y;
        if (nowx == nowy)
            for (int i = 0; i < nowx; ++i)
                chk_min(f[s][nowx], f[s][i] + (nowx - i) * (nowx - i));
        for (int i = 1; i <= m; ++i)
            if (num[i] < buc[a[i].x][a[i].y])
                for (int j = 0; j <= n; ++j)
                    chk_min(f[s + pre[i]][j], f[s][j]);
    }
    printf("%d\n", f[pre[m + 1] - 1][n] - init_edge);

    return 0;
}

T1、迷路

$ n個の\回数M(n個の\当量5、m個の\当量10 ^ 5)の$ マトリックス\(0 \)行くことができない格子を表し、\(1 \)は、格子は、右、下、だけ上がることができ表します三の方向に行きます。Q(Qの\の当量5 \が$ 10倍^ 4)$ 操作は、二つの動作がある:
1、格子の変性タイプは、
2、$からクエリ(a、b)は$に達したかどうかを\((Cを、D)\) ()後者を確実にするために、前者の右側に。

\(ソル\)

観測された\(N- \のLeq 5 \) それは非常に重要なヒントです。
セグメントツリーは、横方向に内蔵され、列の格子列があるべきであるマトリックスで独自にそれを取ることができない場合、特に、カラム上の通信ポイントとの間の関係を記述する行列として各セグメントツリーのリーフノード到達不能として表さ、列挙合成中間点\(N ^ 3 \)合成暴力。
2つの点が変更書かれ、その後消えて変更することがあり、このコラム次のコラム、共感の合併に記述されている各行列のリーフノードの接続関係:しかし、私は診察室に大型練習定数を書きました。

\(出典\)

#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}

const int N = 2e5 + 5;

int n, m, q, mp[7][N];

template<typename T>inline void chk_min(T &_, T __) {
    _ = !~_ ? __ : (_ < __ ? _ : __);
}
struct node {
    int a[6][6];
    node () {
        memset(a, -1, sizeof(a));
    }
    inline int* operator [] (const int x) {
        return a[x];
    }
    inline node operator + (node b) const {
        node ret;
        for (int k = 1; k <= n; ++k)
            for (int i = 1; i <= n; ++i)
                if (~a[i][k])
                    for (int j = 1; j <= n; ++j)
                        if (~b[k][j])
                            chk_min(ret[i][j], a[i][k] + b[k][j] + 1);
        return ret;
    }
} ;
struct segment_tree {
    node t[N << 2];
    void init(int x, int p) {
        for (int i = 1, j; i <= n; ++i) {
            for (j = i; j <= n && mp[j][x]; ++j)
                t[p][i][j] = j - i;
            for (; j <= n; ++j)
                t[p][i][j] = -1;
            for (j = i; j && mp[j][x]; --j)
                t[p][i][j] = i - j;
            for (; j; --j)
                t[p][i][j] = -1;
        }
    }
    inline void push_up(int p) {
        t[p] = t[p << 1] + t[p << 1 | 1];
    }
    void build(int tl, int tr, int p) {
        if (tl == tr)
            return init(tl, p);
        int mid = (tl + tr) >> 1;
        build(tl, mid, p << 1), build(mid + 1, tr, p << 1 | 1);
        push_up(p);
    }
    void modify(int pos, int tl, int tr, int p) {
        if (tl == tr)
            return init(tl, p);
        int mid = (tl + tr) >> 1;
        if (mid >= pos)
            modify(pos, tl, mid, p << 1);
        else
            modify(pos, mid + 1, tr, p << 1 | 1);
        push_up(p);
    }
    node query(int l, int r, int tl, int tr, int p) {
        if (l <= tl && tr <= r)
            return t[p];
        int mid = (tl + tr) >> 1;
        if (mid < l)
            return query(l, r, mid + 1, tr, p << 1 | 1);
        if (mid >= r)
            return query(l, r, tl, mid, p << 1);
        return query(l, r, tl, mid, p << 1) +
               query(l, r, mid + 1, tr, p << 1 | 1);
    }
} T;

int main() {
    //freopen("in", "r", stdin);
    freopen("maze.in", "r", stdin);
    freopen("maze.out", "w", stdout);
    n = in(), m = in(), q = in();
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            mp[i][j] = in();
    T.build(1, m, 1);

    int a, b, c, d;
    node res;
    while (q--) {
        if (in() == 1) {
            a = in(), b = in();
            mp[a][b] ^= 1;
            T.modify(b, 1, m, 1);
        } else {
            a = in(), b = in(), c = in(), d = in();
            res = T.query(b, d, 1, m, 1);
            printf("%d\n", res[a][c]);
        }
    }
    return 0;
}

T2、漢王蒙

そこ平面\(N \(N \の当量 10 ^ 5)\) 黒ドット、\(N- \のLeq 10 ^ Mの\(5)\)白点、黒点\(X \)と白色点\(Y \)以下のマンハッタン距離である\(D \(D \のLeq 10 ^ 9)\)次いで\(X \)する(Y \)を\エッジ、または接続されている\(Y \)をする(Xを\ \)でもエッジ。
不確実性の色点との間にも同じ方向、少なくとも最小および最大の黒ドットと白ドット3員環を見つけます。

\(ソル\)

明らかに、3員環上の同じ色の2つの点があります。
でもライス側は、我々は気にしない方法ですので、唯一のリングのエッジなので、同じ黒と白のドット共感するかどうかを気に。
\は(カバー(x)は\)を表し\(X \)の白色点をカバーすることができ、\(カバー(X、Y)を\)を表し\(X、Yの\)を一緒にホワイトポイントをカバーします。
以下の実施例の最大値(最小共感)。

\ [\開始{整列}回答=&\のsum_ {X、SのY \} \最大\ {カバー(X) - カバー{(X、Y)}、カバー(Y) - カバー(x、y)を\ } \\ =&\のsum_ {X、SのY \} \最大\ {カバー(x)は、カバー(Y)\} - \ sum_ {X、SのY \}カバー(x、y)は\\ = &\ sum_ {SにおけるX、Yの\} \最大\ {カバー(x)は、カバー(Y)\} - \ sum_ {X \ Sにおける} \ binom {カバー(X)} {2} \\ \端{ALIGN} \]

限り計算した\(カバーは、(X)\) ;とすることができる
ライン上の走査線のメンテナンス。

\(出典\)

#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 1e5 + 5;
struct node {
    int x, y;
} a[N], b[N];
struct options {
    long long x; int y, z, id;
    int typ;
} opt[N * 3];

int n, m, d, nn, cntx[N], cnty[N];
long long ans_min, ans_max;

long long tmp[N * 3];
inline bool cmpx(const options &i, const options &j) {
    if (i.x == j.x)
        return i.typ == 0 ? 1 : j.typ != 0;
    return i.x < j.x;
}
void prep() {
    nn = 0;
    for (int i = 1; i <= m; ++i)
        tmp[i] = b[i].y;
    for (int i = 1; i <= n; ++i)
        tmp[i + m] = 1ll * a[i].y - d;
    for (int i = 1; i <= n; ++i)
        tmp[i + n + m] = 1ll * a[i].y + d;
    std::sort(tmp + 1, tmp + 1 + n + n + m);
    nn = std::unique(tmp + 1, tmp + 1 + n + n + m) - tmp - 1;
    for (int i = 1; i <= m; ++i) {
        opt[i].typ = opt[i].id = 0;
        opt[i].x = b[i].x;
        opt[i].y = std::lower_bound(tmp + 1, tmp + 1 + nn, b[i].y) - tmp;
        opt[i].z = 0;
    }
    for (int i = 1; i <= n; ++i) {
        opt[i + m].typ = -1;
        opt[i + m].id = i;
        opt[i + m].x = 1ll * a[i].x - d - 1;

        opt[i + n + m].typ = 1;
        opt[i + n + m].id = i;
        opt[i + n + m].x = 1ll * a[i].x + d;
        
        opt[i + m].y = opt[i + n + m].y = std::lower_bound(tmp + 1, tmp + 1 + nn, 1ll * a[i].y - d) - tmp;
        opt[i + m].z = opt[i + n + m].z = std::lower_bound(tmp + 1, tmp + 1 + nn, 1ll * a[i].y + d) - tmp;
    }
    std::sort(opt + 1, opt + 1 + n + n + m, cmpx);
}

struct binary_index_tree {
    int a[N * 3];
    inline void init() { memset(a, 0, sizeof(a)); }
    void insert(int p, int k) { for (; p <= nn; p += (p & -p)) a[p] += k; }
    int ask(int p, int ret = 0) { for (; p; p -= (p & -p)) ret += a[p]; return ret; }
} bit;

void work() {
    prep();
    bit.init();
    for (int i = 1; i <= n + n + m; ++i) {
        if (!opt[i].typ) {
            bit.insert(opt[i].y, 1);
        } else {
            cntx[opt[i].id] += opt[i].typ * (bit.ask(opt[i].z) - bit.ask(opt[i].y - 1));
        }
    }
}

int main() {
    //freopen("in", "r", stdin);
    freopen("mhw.in", "r", stdin);
    freopen("mhw.out", "w", stdout);
    n = in(), m = in(), d = in();
    for (int i = 1; i <= n; ++i) {
        a[i] = (node){in(), in()};
        a[i] = (node){a[i].x + a[i].y, a[i].x - a[i].y};
    }
    for (int i = 1; i <= m; ++i) {
        b[i] = (node){in(), in()};
        b[i] = (node){b[i].x + b[i].y, b[i].x - b[i].y};
    }
    work();
    std::swap(a, b);
    std::swap(n, m);
    std::swap(cntx, cnty);
    work();

    std::sort(cntx + 1, cntx + 1 + n);
    std::sort(cnty + 1, cnty + 1 + m);
    for (int i = 1; i <= n; ++i) {
        ans_min += 1ll * cntx[i] * (n - i);
        ans_min -= 1ll * cntx[i] * (cntx[i] - 1) / 2;
        ans_max += 1ll * cntx[i] * (i - 1);
        ans_max -= 1ll * cntx[i] * (cntx[i] - 1) / 2;
    }
    for (int i = 1; i <= m; ++i) {
        ans_min += 1ll * cnty[i] * (m - i);
        ans_min -= 1ll * cnty[i] * (cnty[i] - 1) / 2;
        ans_max += 1ll * cnty[i] * (i - 1);
        ans_max -= 1ll * cnty[i] * (cnty[i] - 1) / 2;
    }
    printf("%lld %lld\n", ans_min, ans_max);
    return 0;
}

T1、工場

すべての極大マッチング部グラフが完全に一致しているように、二部グラフを考えるとは縁取りすることができます。

\(ソル\)

一つのことを証明するために:ユニコムポイントとエッジがさえ満ちている場合にのみ、各ブロックでこの二部グラフについて同じ場合は\((A)\) 質問の意味を満たすために\((B)\) \(B \)する(\)を\明らかであろ、省略する。\(\)する(B \)を\の矛盾によるものであってもよい:2点が反対側ユニコムブロックがある場合(\ \ )、\ (B \)が見つかり縁部との間に接続されていない\(\)する(B \)\経路を、辺の集合に一致する辺の奇数が見つかり、この偉大に基づいて一致を見つけるために、添加しました完全一致でなければならない。とらパスに設定され、奇数側一致縁、辺の偶数を添加するが、今回は最大の一致は完全一致ではありません。\(QED \)検討ユニコム、いくつかのブロックは、次のようにマージされます\((\ SUMのX_I)^ 2 \) \(F_ {S、i}は \) 現在のブロックが選択されたリンクとして設定されていることを示している\(S \) 及び点に配置されている\(2I \) これらは、上記の基準を満たすように)ユニコムブロックを最小のコスト。二つの方法を転送します。








\ [\開始{整列} F_ {S \ bigcup {X}、I} =&\分(F_ {S \ bigcup {X}、I}、F_ {S、I})\\ F_ {S、I} =&\ MIN_ {J = 0 } ^ {I - 1} \ {F_ {S、J} +(I - J)^ 2 \} \、(\ Sigma_ {X \ s内} = \ Sigma_ {Y \ \端{整列)} Sで
\]} すぎるすぎる状態の数ので、圧力のような方法を最適化することができないので、
我々は懸念しているだけユニコム各ブロック(左右分類点)数があり、変更することができます状態の数を最適化するために、小数、
話題と人々は私たちに最も言われています\(172032 \)の状態を。

\(出典\)

#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 35, inf = 0x3f3f3f3f;

int n, m, mp[N << 1][N << 1], tmp_x, tmp_y, init_edge, buc[N][N], pre[N << 1], f[172033][N], num[N << 1];
bool vis[N << 1];

struct node {
    int x, y;
    inline bool operator < (const node &b) const {
        return buc[this->x][this->y] < buc[b.x][b.y];
    }
    inline bool operator == (const node &b) const {
        return this->x == b.x && this->y == b.y;
    }
} a[N + N];

void prep(const int u) {
    vis[u] = 1;
    if (u <= n)
        ++tmp_x;
    else
        ++tmp_y;
    for (int i = 1; i <= mp[u][0]; ++i)
        if (!vis[mp[u][i]])
            prep(mp[u][i]);
}

int main() {
    //freopen("in", "r", stdin);
    freopen("factory.in", "r", stdin);
    freopen("factory.out", "w", stdout);
    n = in();
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j) {
            char c = getchar();
            while (c < '0' || c > '1')
                c = getchar();
            if (c == '1') {
                mp[i][++mp[i][0]] = j + n;
                mp[j + n][++mp[j + n][0]] = i;
            }
            init_edge += c == '1';
        }
    for (int i = 1; i <= n + n; ++i)
        if (!vis[i]) {
            tmp_x = tmp_y = 0;
            prep(i);
            ++buc[tmp_x][tmp_y];
        }

    for (int i = 0; i <= n; ++i)
        for (int j = 0; j <= n; ++j)
            if (buc[i][j])
                a[++m] = (node){i, j};
    std::sort(a + 1, a + 1 + m);
    m = std::unique(a + 1, a + 1 + m) - a - 1;
    pre[1] = 1;
    for (int i = 2; i <= m + 1; ++i)
        pre[i] = pre[i - 1] * (buc[a[i - 1].x][a[i - 1].y] + 1);

    memset(f, inf, sizeof(f));
    f[0][0] = 0;

    int nowx, nowy;
    for (int s = 0; s < (pre[m + 1]); ++s) {
        nowx = nowy = 0;
        for (int i = 1; i <= m; ++i)
            num[i] = s % pre[i + 1] / pre[i];
        for (int i = 1; i <= m; ++i)
            nowx += num[i] * a[i].x, nowy += num[i] * a[i].y;
        if (nowx == nowy)
            for (int i = 0; i < nowx; ++i)
                chk_min(f[s][nowx], f[s][i] + (nowx - i) * (nowx - i));
        for (int i = 1; i <= m; ++i)
            if (num[i] < buc[a[i].x][a[i].y])
                for (int j = 0; j <= n; ++j)
                    chk_min(f[s + pre[i]][j], f[s][j]);
    }
    printf("%d\n", f[pre[m + 1] - 1][n] - init_edge);

    return 0;
}

おすすめ

転載: www.cnblogs.com/15owzLy1-yiylcy/p/11348880.html