《信息学奥赛一本通·高手专项训练》集训 Day 2

深度搜索

100+100+70=270/Rank 7\color{Green}100\color{Black}+\color{Green}100\color{Black}+\color{#92E411}70\color{Black}=\color{#92E411}270\color{Black}\text{/Rank 7}

A.入门数学\color{#FFC116}\text{A.入门数学}

题目

我们有一个 nnmm 列的矩阵 AA,第 ii 行第 jj 列的数记作 Ai,jA_{i,j}。同时给定了一个长度为 mm 的序列 xx

定义一个长度为 k(1kn)k(1\le k\le n) 的整数序列 pp 是“好”,当且仅当其满足如下条件:

  • 序列中每个数均满足 1pin)1\le p_i\le n)
  • ijpipji\neq j\leftrightarrow p_i\neq p_j
  • 1im,Ap1,iAp2,iApk,i=xi\forall 1\le i\le m,A_{p_1,i}\bigoplus A_{p_2,i}\bigoplus……\bigoplus A_{p_k,i}=x_i,其中 \bigoplus 为一种位运算(& 是与,| 是或,^ 是异或)。

现在给定 AAxx 以及 \bigoplus,请你求出有多少满足条件的序列 pp

两个序列不同,当且仅当它们的长度不同,或者存在一个位置不同。

这个答案可能很大,请你求出对 109+910^9+9 取模的结果。

题解

可以发现每一行的数都是捆绑在一起的,注意到 1n,m251\le n,m\le 25,我们可以把每一行的数状态压缩为一个,这样就相当于从 2525 个数中选任意个使他们经过 \bigoplus 运算后的结果为 xx,按编号从小到大深搜即可。

有个坑点是同样长度同样数字不同的位置也是不同的,所以一个组合对答案的贡献是他的全排列,预处理阶乘即可。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 30;
const ll mod = 1e9 + 9;
int n, m, a[N][N], x[N], v[N], cnt[N], p[N];
vector<int> e;
int wan;
ll ans, jc[N];
char c;
void dfs1(int s, int now, int res) {
    if (now > n) {
        if (res == wan && s)
            ans = (ans + jc[s]) % mod;
        return;
    }
    dfs1(s, now + 1, res);
    dfs1(s + 1, now + 1, res & p[now]);
}
void dfs2(int s, int now, int res) {
    if (now > n) {
        if (res == wan && s)
            ans = (ans + jc[s]) % mod;
        return;
    }
    dfs2(s, now + 1, res);
    dfs2(s + 1, now + 1, res | p[now]);
}
void dfs3(int s, int now, int res) {
    if (now > n) {
        if (res == wan && s)
            ans = (ans + jc[s]) % mod;
        return;
    }
    dfs3(s, now + 1, res);
    dfs3(s + 1, now + 1, res ^ p[now]);
}
int main() {
    freopen("xx.in", "r", stdin);
    freopen("xx.out", "w", stdout);
    cin >> c;
    jc[1] = 1;
    for (int i = 2; i <= 26; i++) jc[i] = jc[i - 1] * i % mod;
    n = read();
    m = read();
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            a[i][j] = read();
        }
    }
    for (int i = 1; i <= m; i++) {
        x[i] = read();
    }
    if (c == '&') {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                p[i] |= (a[i][j] << (j - 1));
            }
        }
        for (int i = 1; i <= m; i++) wan |= (x[i] << (i - 1));
        dfs1(0, 1, (1 << m) - 1);
        cout << ans << endl;
    } else if (c == '|') {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                p[i] |= (a[i][j] << (j - 1));
            }
        }
        for (int i = 1; i <= m; i++) wan |= (x[i] << (i - 1));
        dfs2(0, 1, 0);
        cout << ans << endl;
    } else if (c == '^') {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                p[i] |= (a[i][j] << (j - 1));
            }
        }
        for (int i = 1; i <= m; i++) {
            wan |= (x[i] << (i - 1));
        }
        dfs3(0, 1, 0);
        cout << ans << endl;
    }
    return 0;
}

B. 购买汽水\color{#FFC116}\text{B. 购买汽水}

题目

暑期一共 NN 天,好心人小涛想给大家买汽水,小涛最多只能给大家花 MM 元钱。由于每天汽水的价格都不固定,现在给出每天买汽水的花销,我们可以随意选择让小涛哪些天买汽水(当然总花费不能超过 MM)。请问最多一共能够花掉小涛多少钱呢?

题解

如果 MM 很小,直接背包就可以了,但这题 MM 很大,NN 很小,我们考虑直接搜索每天的汽水选或不选,暴力搜索是 O(2n)O(2^n) 的,我们可以折半搜索,再用双指针匹配两半的汽水即可,时间复杂度 O(2n2+1)O(2^{\frac{n}{2}+1})

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 50;
int n;
vector<ll> e[2];
ll m, p[N], ans;
void dfs(int t, int now, int limit, ll mx) {
    if (now > limit) {
        e[t].push_back(mx);
        return;
    }
    dfs(t, now + 1, limit, mx);
    if (mx + p[now] <= m)
        dfs(t, now + 1, limit, mx + p[now]);
}
int main() {
    freopen("drink.in", "r", stdin);
    freopen("drink.out", "w", stdout);
    n = read();
    m = read();
    for (int i = 1; i <= n; i++) {
        p[i] = read();
    }
    if (n < 2) {
        if (p[1] <= m)
            cout << p[1] << endl;
        else
            cout << 0 << endl;
        return 0;
    }
    dfs(0, 1, n / 2, 0);
    dfs(1, n / 2 + 1, n, 0);
    sort(e[0].begin(), e[0].end());
    sort(e[1].begin(), e[1].end());
    for (int i = 0, j = e[1].size() - 1; i < e[0].size(); i++) {
        while (j >= 0 && e[0][i] + e[1][j] > m) {
            j--;
        }
        if (j >= 0 && e[0][i] + e[1][j] <= m) {
            ans = max(ans, e[0][i] + e[1][j]);
        }
    }
    write(ans);
    return 0;
}

C. 宝石布阵\color{#3498DB}\text{C. 宝石布阵}

题目

魔法阵是一个 n×mn\times m 的格子(高 nn,宽 mm),n×mn\times m 为偶数。佳佳手中有 n×mn\times m 个宝石(以 1n×m1\sim n\times m 编号)。佳佳从最右上角的格子开始走,从一个格子可以走到上、下、左、右 44 个相邻的格子,但不能走出边界。每个格子必须且仅能到过 11 次,这样佳佳一共走了 n×mn\times m 个格子停止(随便停哪里)。佳佳每进入一个格子,就在该格子里放入一颗宝石。他是按顺序放的,也就是说——第 ii 个进入的格子放入 ii 号宝石。

如果两颗宝石的编号对 n×m2\frac{n\times m}{2} 取模的值相同,则认为这两颗宝石相互之间有微妙的影响。也就是说,我们按照宝石的编号对 n×m2\frac{n\times m}{2} 取模的值,将宝石分成 n×m2\frac{n\times m}{2} 对,其中每对都恰有两颗宝石。对于每一对宝石,设第一颗宝石在第 aa 行第 bb 列,另一颗宝石在第 cc 行第 dd 列,那么定义这 22 个宝石的魔力影响值为 k1×ac+k2×bdk1\times|a-c|+k2\times |b-d|

需要你求出的是,在所有合乎题意的宝石摆放方案中,所有成对的宝石间的最大魔力影响值的最小值为多少。换句话说,如果我们定义对 n×m2\frac{n\times m}{2} 取模的值为 ii 的一对宝石的魔力影响值为 aia_i。你需要求出的就是 max{aii=0,1,2,n×m21}\max\{a_i|i=0,1,2,…\frac{n\times m}{2}-1\} 的最小值。

题解

起点为 (1,m)(1,m),由于 n×mn\times m 不超过 5050,我们可以直接搜索路径,求出每种路径的最大值并取最小值。

但是还是可以剪枝的:

  • 可行性剪枝:若要走的这个点的上下都被走过而左右没有,或左右都被走过而上下没有,则这个点不能选。以前者为例,若上下都被走过而左右没有,因为是同一条路,所以上下必然有另一条路联通,而左右没有说明这个点将网格分成了两个空闲网格,不符合要求。后者亦是如此。
  • 最优性剪枝:若当前得出来的最大值已经不小于已算过的路径的最小值,则对答案没有改进的机会,不必搜索下去了。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 60;
int n, m, k1, k2, ans = 1e9;
int fx[4] = { 0, 0, 1, -1 };
int fy[4] = { 1, -1, 0, 0 };
int v[N][N], a[N * N][5], tot[N * N], b[N][N];
bool check(int x, int y) {
    if (x > 0 && x <= n && y > 0 && y <= m && !v[x][y])
        return 1;
    return 0;
}
void dfs(int x, int y, int t, int mx) {
    if (mx >= ans)
        return;
    if (t == n * m + 1) {
        int mz = 0;
        for (int i = 0; i < n * m / 2; i++) {
            mz = max(mz, k1 * abs(a[i][1] - a[i][3]) + k2 * abs(a[i][2] - a[i][4]));
        }
        ans = min(ans, mz);
        return;
    }
    for (int i = 0; i < 4; i++) {
        int xx = x + fx[i], yy = y + fy[i];
        if (!check(xx, yy))
            continue;
        int my = mx;
        if (check(xx + 1, yy) && check(xx - 1, yy) && !check(xx, yy - 1) && !check(xx, yy + 1))
            continue;
        if (!check(xx + 1, yy) && !check(xx - 1, yy) && check(xx, yy - 1) && check(xx, yy + 1))
            continue;
        if (tot[t % (n * m / 2)] == 2)
            my = max(my, k1 * abs(a[t % (n * m / 2)][1] - xx) + k2 * abs(a[t % (n * m / 2)][2] - yy));
        v[xx][yy] = 1;
        a[t % (n * m / 2)][++tot[t % (n * m / 2)]] = xx;
        a[t % (n * m / 2)][++tot[t % (n * m / 2)]] = yy;
        dfs(xx, yy, t + 1, my);
        v[xx][yy] = 0;
        tot[t % (n * m / 2)]--;
        tot[t % (n * m / 2)]--;
    }
}
int main() {
    freopen("mmatrix.in", "r", stdin);
    freopen("mmatrix.out", "w", stdout);
    n = read();
    m = read();
    k1 = read();
    k2 = read();
    v[1][m] = 1;
    a[1][1] = 1;
    a[1][2] = m;
    tot[1] = 2;
    dfs(1, m, 2, 0);
    cout << ans << endl;
    return 0;
}

广度搜索

60+86+30=176/Rank 6\color{#92E411}60\color{Black}+\color{#5EB95E}86\color{Black}+\color{Orange}30\color{Black}=\color{#5EB95E}176\color{Black}\text{/Rank 6}

A. 游走距离\color{#52C41A}\text{A. 游走距离}

题目

在比特镇一共有 nn 个街区,编号依次为 11nn,它们之间通过若干条单向道路连接。

比特镇的交通系统极具特色,除了 mm 条单向道路之外,每个街区还有一个编码 valival_i,不同街区可能拥有相同的编码。如果 vali&valj=valjval_i\&val_j=val_j,即 在二进制下与 valjval_j 做与运算等于 valjval_j,那么也会存在一条额外的从 ii 出发到 jj 的单向道路。

Byteasar 现在位于 11 号街区,他想知道通过这些道路到达每一个街区最少需要多少时间。因为比特镇的交通十分发达,你可以认为通过每条道路都只需要 11 单位时间。

题解

因为 nn 很大,所以极限情况下会有 n2n^2 条额外边,直接建全会超时。

可以分析得出:如果有一条额外边 iji\rightarrow j,那么 valjval_j 必然是 valival_i 的子集,而子集的子集还是子集,故只要知道每个值的直接子集(二进制表示下仅少一个 11),于是我们可以再建 2202^{20} 个虚点 n+1+0n+1+2201n+1+0\sim n+1+2^{20}-1,在有直接子集关系的数中连单向边,边权为 00

把所有的点 iin+1+valin+1+val_i 连双向边,使 in+1+valii\rightarrow n+1+val_i 的权值为 11n+1+valiin+1+val_i\rightarrow i 的权值为 00,这样从点 ii 就可以只消耗 11 边权就可以到达所有与 ii 有子集关系的点 jj,符合题目意思。

由于边权有两种,把 bfs\text{bfs} 的队列改成优先队列即可。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 3e5 + 10, M = 1e6 + 10, S = 12;
int n, m, val[N], len[N + M];
int head[N + M], ver[N + M * S], nxt[N + M * S], edge[N + M * S], tot;
priority_queue<pair<int, int> > q;
void add(int x, int y, int z) {
    ver[++tot] = y;
    edge[tot] = z;
    nxt[tot] = head[x];
    head[x] = tot;
}
void bfs() {
    q.push(make_pair(0, 1));
    for (int i = 1; i <= n + (1 << 20); i++) len[i] = -1;
    len[1] = 0;
    while (q.size()) {
        int x = q.top().second;
        q.pop();
        for (int i = head[x]; i; i = nxt[i]) {
            int y = ver[i];
            if (len[y] >= 0)
                continue;
            len[y] = len[x] + edge[i];
            q.push(make_pair(-len[y], y));
        }
    }
}
int main() {
    n = read();
    m = read();
    for (int i = 1; i <= n; i++) {
        val[i] = read();
        add(i, n + val[i] + 1, 1);
        add(n + val[i] + 1, i, 0);
    }
    for (int i = 1; i <= m; i++) {
        int u, v;
        u = read();
        v = read();
        add(u, v, 1);
    }
    for (int i = 0; i < (1 << 20); i++)
        for (int j = 0; j < 20; j++)
            if ((i >> j) & 1)
                add(n + i + 1, n + i - (1 << j) + 1, 0);
    bfs();
    for (int i = 1; i <= n; i++)
        cout << len[i] << endl;
    return 0;
}

B. 道路航线\color{#52C41A}\text{B. 道路航线}

题目

Z 国有 nn 个城市,标号为 1n1\sim n

城市之间的交通可以通过道路或航线,其中道路是双向的,但是航线是单向的。

最初始的时候这些城市之间没有道路和航线相连,然后 Z 国的基建部门依次修建了 mm 条道路或航线。

小明与小红是好朋友,小明家住编号为 aa 的城市,小红家住编号为 bb 的城市。

小明想知道,在基建部门建设的第几条道路或航线后从 aa 城可以到 bb 城。

小红想知道,在基建部门建设的第几条道路或航线后从 bb 城可以到 aa 城。 帮帮他们吧。

保证修建完 mm 条道路或航线后既可以从 aabb ,也可以从 bbaa

题解

由于 n,mn,m 高达 3×1063\times 10^6,需要 O(n)O(n) 的算法,所以我们只能一条边一条边地加,实时更新到达的情况。

aabb 为例求解,把当前所有 aa 可以到达的点打上标记,对于新加入的边,如果是道路我们就把它拆成两条航线来做。

设加入的单向边为 uvu\rightarrow v

  • u,vu,v 都能被 aa 访问到,则这条边没用处。
  • uu 能,vv 不能,则这条边能联通 u,vu,v,从 vv 开始再 bfs\text{bfs},标记能访问到的点。
  • u,vu,v 都不能,则这条边可能在下次的上一个情况中起作用,把它加入到图中。

不断加边,直到 bb 被标记到即可。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 3e6 + 10;
int n, m, a, b, bi[N][3];
int head[N], ver[N << 1], nxt[N << 1], tot;
bool v[N];
void add(int x, int y) {
    ver[++tot] = y;
    nxt[tot] = head[x];
    head[x] = tot;
}
bool check(int s, int t, int ed) {
    if (v[s] && v[t])
        return 0;
    if (v[s] && !v[t]) {
        v[t] = 1;
        queue<int> q;
        q.push(t);
        while (q.size()) {
            int x = q.front();
            q.pop();
            for (int i = head[x]; i; i = nxt[i]) {
                int y = ver[i];
                if (!v[y]) {
                    v[y] = 1;
                    q.push(y);
                }
            }
            head[x] = 0;
        }
    }
    if (!v[s])
        add(s, t);
    return v[ed];
}
int solve(int s, int t) {
    v[s] = 1;
    for (int i = 1; i <= m; i++) {
        if (check(bi[i][0], bi[i][1], t))
            return i;
        if (bi[i][2] == 0) {
            if (check(bi[i][1], bi[i][0], t))
                return i;
        }
    }
    return m;
}
int main() {
    n = read();
    m = read();
    a = read();
    b = read();
    for (int i = 1; i <= m; i++) {
        bi[i][0] = read();
        bi[i][1] = read();
        bi[i][2] = read();
    }
    cout << solve(a, b) << " ";
    tot = 0;
    memset(head, 0, sizeof(head));
    memset(ver, 0, sizeof(ver));
    memset(nxt, 0, sizeof(nxt));
    memset(v, 0, sizeof(v));
    cout << solve(b, a) << " ";
    return 0;
}

C. 选择信封\color{#3498DB}\text{C. 选择信封}

题目

小明和小方玩一个挑卡片游戏。

小明会给出 NN 对信封,每个信封里有两张不同颜色的卡片,他会让小方从中挑选任意个信封,但是一对信封中最多只能挑选一个,(信封是透明的,可以看到里面卡片颜色)。等小方挑好后,小明会尝试从小方挑出的信封中再选出若干个(不可以不取),把其中的卡片取出,若存在一种方案使得取出的卡片中,每种颜色的卡片都有偶数张,那么小明就赢了。小方想知道在自己赢的前提下,他最多能选出多少信封。

题解

颜色数很大,而信封很少,所以可以先将颜色离散化。

如果直接看题目,很难想出解法,但如果把卡片看成点,信封看成边,题目就变成了选最多的边使这些边构成的图中没有环。

枚举每对边尝试加入,能加哪个就加哪个,如果两边都加不了,就改变之前的选择,仿照二分图匹配寻找增广路(未选边 \rightarrow 已选边 \rightarrow 未选边 \rightarrow…… 未选边),将所有边的选择情况取反。

增广其实也可以理解为边的替换关系,选了一条未选边,就得替换一条已选边,已选边又可能和另一条未选边有替换关系,以此类推,直到找到一条两端不连通的未选边(选了这条边后不形成环)。所以增广的连边有两种情况:

  • 在一对信封(边)中相互连边,显然不选这封就可选那封。
  • 对于一条未选择的边,如果他两端连通(选了这条边后形成环),在他与环上的其它点连边,因为选了这条边后环上其他边必须去掉一条。

循环 nn 次一次加入所有对信封后把所有可选边计数得到答案。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 610;
int n, m, fa[N << 2], b[N << 2], from[N << 1], visp[N << 2], ans;
bool ok[N << 1], v[N << 1], ring[N << 1];
struct asdf {
    int u, v, use;
} e[N << 1];
struct ep {
    int v, p;
};
vector<ep> head[N << 2];
vector<int> ti[N << 1];
queue<int> q;
int get(int x) { return (x == fa[x] ? x : fa[x] = get(fa[x])); }
bool can_insert(int x) {
    int u = get(e[x].u), v = get(e[x].v);
    if (u == v)
        return 0;
    fa[u] = v;
    e[x].use = 1;
    return 1;
}
bool is_ring(int now, int t, int sp) {
    if (now == t)
        return 1;
    visp[now] = sp;
    for (int i = 0; i < head[now].size(); i++) {
        int y = head[now][i].v;
        if (visp[y] == sp)
            continue;
        ring[head[now][i].p] = 1;
        if (is_ring(y, t, sp))
            return 1;
        ring[head[now][i].p] = 0;
    }
    return 0;
}
void build(int x) {
    memset(ok, 0, sizeof(ok));
    memset(visp, -1, sizeof(visp));
    for (int i = 0; i <= x; i++) ti[i].push_back(i ^ 1);
    for (int i = 0; i <= x; i++)
        if (!e[i].use) {
            ok[i] = 1;
            memset(ring, 0, sizeof(ring));
            if (!is_ring(e[i].u, e[i].v, i))
                continue;
            ok[i] = 0;
            for (int j = 0; j < x; j++)
                if (ring[j])
                    ti[i].push_back(j);
        }
}
bool bfs(int x) {
    memset(v, 0, sizeof(v));
    memset(from, -1, sizeof(from));
    int end = -1;
    q.push(x);
    v[x] = 1;
    while (q.size()) {
        int p = q.front();
        q.pop();
        for (int i = 0; i < ti[p].size(); i++) {
            int y = ti[p][i];
            if (v[y])
                continue;
            v[y] = 1;
            if (e[p].use ^ e[y].use) {
                from[y] = p;
                if (ok[y]) {
                    while (q.size()) q.pop();
                    end = y;
                    break;
                }
                q.push(y);
            }
        }
    }
    if (end == -1)
        return 0;
    for (; end != -1; end = from[end]) e[end].use ^= 1;
    return 1;
}
void init(int t) {
    for (int i = 0; i <= m; i++) fa[i] = i;
    for (int i = 0; i < 4 * n; i++) head[i].clear();
    for (int i = 0; i < 2 * n; i++) ti[i].clear();
    for (int i = 0; i < 2 * t; i++) {
        if (e[i].use) {
            fa[get(e[i].u)] = get(e[i].v);
            head[e[i].u].push_back((ep){ e[i].v, i });
            head[e[i].v].push_back((ep){ e[i].u, i });
        }
    }
}
int main() {
    n = read();
    for (int i = 0; i < 2 * n; i++) {
        e[i].u = read();
        e[i].v = read();
        b[i * 2 + 1] = e[i].u;
        b[i * 2 + 1 + 1] = e[i].v;
    }
    sort(b + 1, b + 4 * n + 1);
    m = unique(b + 1, b + 4 * n + 1) - b - 1;
    for (int i = 0; i < 2 * n; i++) {
        e[i].u = lower_bound(b + 1, b + m + 1, e[i].u) - b;
        e[i].v = lower_bound(b + 1, b + m + 1, e[i].v) - b;
    }
    for (int i = 0; i < n; i++) {
        init(i);
        if (can_insert(i * 2))
            continue;
        if (can_insert(i * 2 + 1))
            continue;
        build(i * 2 + 1);
        if (bfs(i * 2))
            continue;
        bfs(i * 2 + 1);
    }
    for (int i = 0; i < 2 * n; i++) ans += e[i].use;
    write(ans);
    return 0;
}
posted @ 2022-08-02 12:28  luckydrawbox  阅读(103)  评论(0)    收藏  举报  来源