POI-3

T1

洛谷 P3451 ATR-Tourist Attractions

首先求出关键点间两两的最短路,然后状压 dp。\(dp[i][S]\) 表示当前在 \(i\),已经走过了 \(S\) 这个集合的最小距离。洛谷上卡空间,学校 OJ 上不卡,所以就没优化空间。

会 MLE 的代码
#include <iostream>
#include <string.h>
#include <queue>
using namespace std;
int n, m, K;
int dist[25][20005];
struct node {
    int x, dis;
} tmp;
int head[20005], nxt[400005], to[400005], ew[400005], ecnt;
void add(int u, int v, int ww) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww; }
bool operator<(node a, node b) { return a.dis > b.dis; }
priority_queue<node> q;
bool vis[20005];
void dijkstra(int S) {
    memset(dist[S], 63, sizeof dist[S]);
    memset(vis, 0, sizeof vis);
    q.push((node) { S, dist[S][S] = 0 });
    while (!q.empty()) {
        tmp = q.top();
        q.pop();
        int x = tmp.x;
        if (vis[x]) 
            continue;
        vis[x] = 1;
        for (int i = head[x]; i; i = nxt[i]) {
            int v = to[i];
            if (dist[S][v] > dist[S][x] + ew[i]) 
                q.push((node) { v, dist[S][v] = dist[S][x] + ew[i] });
        }
    }
}
int dp[25][1048605];
int p[25];
int main() {
    cin >> n >> m >> K;
    for (int i = 1; i <= m; i++) {
        int u, v, ww;
        cin >> u >> v >> ww;
        add(u, v, ww);
        add(v, u, ww);
    }
    for (int i = 1; i <= K + 1; i++) dijkstra(i);
    int lcnt;
    cin >> lcnt;
    for (int i = 1; i <= lcnt; i++) {
        int a, b;
        cin >> a >> b;
        p[b - 1] |= (1 << (a - 2));
    }
    memset(dp, 63, sizeof dp);
    for (int i = 1; i <= K; i++) {
        if (!p[i]) 
            dp[i][1 << (i - 1)] = dist[1][i + 1];
    }
    if (K == 0) {
        cout << dist[1][n] << "\n";
        return 0;
    }
    for (int i = 1; i < (1 << K); i++) {
        for (int j = 1; j <= K; j++) {
            if (((i >> (j - 1)) & 1) == 0 && (p[j] & i) == p[j]) {
                for (int k = 1; k <= K; k++) {
                    if ((i >> (k - 1)) & 1) 
                        dp[j][i | (1 << (j - 1))] = min(dp[j][i | (1 << (j - 1))], dp[k][i] + dist[k + 1][j + 1]);
                }
            }
        }
    }
    int ans = 2147483647;
    for (int i = 1; i <= K; i++) ans = min(ans, dp[i][(1 << K) - 1] + dist[i + 1][n]);
    cout << ans  << "\n";
    return 0;
}

T2

洛谷 P3457 POW-The Flood

首先观察到从低往高摆抽水机一定更优。所以直接把所有点排序,加入一个点时将其与所有已加入的与它的四联通的点连边,如果这个点是重要的而且所在连通块中没有抽水机,那就放一个。否则不放。使用并查集维护即可。注意要每次先把一种深度的全部加入再判断和加抽水机。

代码
#include <iostream>
#include <algorithm>
using namespace std;
struct node {
    int x, y, h;
    bool ic;
} a[1000005];
int fa[1000005];
bool vis[1005][1005];
bool h[1000005];
int n, m;
inline int f(int x, int y) { return (x - 1) * m + y; }
int getf(int x) { return (fa[x] == x ? x : (fa[x] = getf(fa[x]))); }
void Merge(int x, int y) {
    x = getf(x), y = getf(y);
    if (x == y) 
        return;
    fa[x] = y;
    h[y] |= h[x];
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            int t = f(i, j);
            fa[t] = t;
            cin >> a[t].h;
            a[t].ic = (a[t].h > 0);
            a[t].h = abs(a[t].h);
            a[t].x = i, a[t].y = j;
        }
    }
    int ans = 0;
    sort(a + 1, a + n * m + 1, [](node a, node b) { return a.h < b.h; });
    for (int i = 1; i <= n * m;) {
        int j = i;
        while (a[j].h == a[i].h) vis[a[j].x][a[j].y] = 1, ++j;
        int t = i;
        for (; i < j; i++) {
            if (vis[a[i].x - 1][a[i].y]) 
                Merge(f(a[i].x, a[i].y), f(a[i].x - 1, a[i].y));
            if (vis[a[i].x][a[i].y - 1]) 
                Merge(f(a[i].x, a[i].y), f(a[i].x, a[i].y - 1));
            if (vis[a[i].x + 1][a[i].y]) 
                Merge(f(a[i].x, a[i].y), f(a[i].x + 1, a[i].y));
            if (vis[a[i].x][a[i].y + 1]) 
                Merge(f(a[i].x, a[i].y), f(a[i].x, a[i].y + 1));
        }
        for (; t < j; t++) {
            if (a[t].ic && !h[getf(f(a[t].x, a[t].y))]) 
                h[getf(f(a[t].x, a[t].y))] = 1, ++ans;
        }
    }
    cout << ans << "\n";
    return 0;
}

T3

洛谷 P3460 Tet-Tetris Attack

维护一个栈,加入一个数时如果这个数在栈中存在,就一直交换直到这两个数相邻然后消掉。否则只把这个数加入栈。可以发现这样就是最优解。

代码
#include <iostream>
using namespace std;
int n;
int p[100005];
pair<int, int> stk[100005], tstk[100005];
int sz, tsz;
bool vis[100005];
int ans[1000005], acnt;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n * 2; i++) {
        cin >> p[i];
        if (vis[p[i]]) {
            while (stk[sz].first != p[i]) {
                ans[++acnt] = sz;
                tstk[++tsz] = stk[sz--];
            }
            vis[p[i]] = 0;
            --sz;
            while (tsz) stk[++sz] = tstk[tsz--];
        } else {
            vis[p[i]] = 1;
            stk[++sz] = make_pair(p[i], i);
        }
    }
    cout << acnt << "\n";
    for (int i = 1; i <= acnt; i++) cout << ans[i] << "\n";
    return 0;
}

T4

洛谷 P3462 ODW-Weights

把每个容量按照给出的砝码重量为位权进行进制分解,然后把所有容量在每一位分出的结果加起来,然后贪心地从小往大加入砝码,把这个砝码重量对应的位 \(-1\),如果不能减了就结束,能减就答案增加。本质是将每个容量分成若干段,每段长度都是一个砝码的重量,然后分成的段数最小,也就是前面一堆段都是长度为最大砝码重的段,接下来是一堆长度为第二大砝码重的,这样分下去,实际上是为每个重量预留下空间。然后每次加入最小的相当于找最小的合适的空间,如果有当前长度的空间就放进去,否则去拆最小的比当前重量的空间拿来用。只是用进制的形式展现。

代码
#include <iostream>
#include <algorithm>
using namespace std;
int n, m;
int a[100005];
int w[100005];
int p[100005], pcnt;
int cnt[100005];
void Divide(int x) {
    for (int i = pcnt; i; i--) {
        cnt[i] += x / p[i];
        x %= p[i];
    }
    cnt[0] += x;
}
bool work(int x) {
    if (cnt[x]) {
        --cnt[x];
        return 1;
    }
    if (x > pcnt || !work(x + 1)) 
        return 0;
    cnt[x] += p[x + 1] / p[x] - 1;
    return 1;
}
int ans = 0;
int main() {
    *p = 1;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> w[i];
    for (int j = 1; j <= m; j++) cin >> a[j];
    sort(a + 1, a + m + 1);
    for (int i = 1; i <= m; i++) (a[i] != a[i - 1] && a[i] != 1) ? (p[++pcnt] = a[i]) : 0;
    for (int i = 1; i <= n; i++) Divide(w[i]);
    a[0] = 1;
    int cnt = 0;
    for (int i = 1; i <= m; i++) {
        cnt += (a[i] != a[i - 1]);
        work(cnt) ? (++ans) : (i = m + 1);
    }
    cout << ans << "\n";
    return 0;
}

T4

洛谷 P3464 WAG-Quaternary Balance

首先四进制拆分。对于每一位有两种情况:从 \(0\) 加上去,和从高位拿一个下来然后从 \(4\) 往下减。从低位往高位 dp,考虑 \(f_{i, 0 / 1}\) 表示到第 \(i\) 位,当前位是从 \(0\) 加上去的或是从 \(4\) 减下来的最小代价。然后分讨转移。方案数就顺带维护一下即可。

代码
#include <iostream>
#define int long long
using namespace std;
const int P = 1000000000;
int f[2005][2], g[2005][2];
int a[2005], n;
signed main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    string str;
    cin >> str;
    for (int i = 0; i < (int)str.size(); i++) {
        for (int j = 0; j <= 2000; j++) a[j] *= 10;
        a[0] += str[i] - '0';
        for (int j = 0; j <= 2000; j++) {
            a[j + 1] += a[j] / 4;
            a[j] %= 4;
        }
    }
    for (n = 2000; a[n] == 0; --n);
    f[0][0] = a[0];
    f[0][1] = 4 - a[0];
    g[0][0] = g[0][1] = 1;
    for (int i = 1; i <= n; i++) {
        int x = f[i - 1][0] + a[i], y = f[i - 1][1] + a[i] + 1;
        if (x < y) 
            f[i][0] = x, g[i][0] = g[i - 1][0];
        else if (x > y) 
            f[i][0] = y, g[i][0] = g[i - 1][1];
        else 
            f[i][0] = x, g[i][0] = (g[i - 1][0] + g[i - 1][1]) % P;
        x = f[i - 1][0] + 4 - a[i], y = f[i - 1][1] + 3 - a[i];
        if (x < y) 
            f[i][1] = x, g[i][1] = g[i - 1][0];
        else if (x > y) 
            f[i][1] = y, g[i][1] = g[i - 1][1];
        else 
            f[i][1] = y, g[i][1] = (g[i - 1][0] + g[i - 1][1]) % P;
    }
    if (f[n][0] < f[n][1] + 1) 
        cout << g[n][0] << "\n";
    else if (f[n][0] > f[n][1] + 1) 
        cout << g[n][1] << "\n";
    else 
        cout << (g[n][0] + g[n][1]) % P << "\n";
    return 0;
}

T6

洛谷 P3470 BBB-BBB

首先初始的最终和不一定为 \(q\),需要调整,这是无论如何都要的代价,记为 \(c_1\)。接下来考虑前缀和的限制。将前缀和画出来,发现是一直波动的一个东西。我们要考虑的就是这个最低的波谷,要让它 \(>0\)。观察到一次 \(-1\)\(+1\) 会让波谷上移两个单位,所以可以算出来这个地方的最小代价。由于要保证和为 \(q\),所以在后面要对应的将 \(+1\) 变成 \(-1\)。所以代价要乘 \(2\)。记这部分代价为 \(c_2\)。由于还有循环移位的存在,我们先倍长原序列,然后枚举偏移量,通过单调队列求出每个长度为 \(n\) 的区间的前缀和最小值。这部分加上 \(p\) 如果还没有到 \(0\),那就需要调整。如果初始的最终和比 \(q\) 小,那说明一开始的调整就会把一些 \(-1\) 变成 \(+1\),正好符合这里调整。所以通过这个也可以将波谷上移。最终的代价就是 \(c_1 + c_2\) 加上循环移位的代价。

代码
#include <iostream>
#define int long long
#define abs(x) ((x) < 0 ? (-(x)) : (x))
using namespace std;
int n, p, q, x, y;
string str;
int S[2000005];
int val[2000005];
pair<int, int> Q[2000005];
int ql, qr;
signed main() {
    cin >> n >> p >> q >> x >> y;
    cin >> str;
    str = ' ' + str;
    ql = 1;
    for (int i = 1; i <= n; i++) S[i] = S[i + n] = (str[i] == '+' ? 1 : -1);
    for (int i = 1; i <= n * 2; i++) S[i] += S[i - 1];
    for (int i = 1; i < n; i++) {
        while (ql <= qr && Q[qr].first > S[i]) --qr;
        Q[++qr] = make_pair(S[i], i);
    }
    for (int i = 1; i <= n; i++) {
        while (ql <= qr && Q[qr].first > S[i + n - 1]) --qr;
        while (ql <= qr && Q[ql].second < i) ++ql;
        Q[++qr] = make_pair(S[i + n - 1], i + n - 1);
        val[i] = Q[ql].first - S[i - 1];
    }
    int m = (q - (p + S[n])) / 2;
    int ans = 2147483647;
    for (int i = 1; i <= n; i++) {
        int c = y * ((n + 1 - i) % n) + abs(m) * x;
        int t = (p + val[i]);
        (m > 0) ? (t += m * 2) : 0;
        (t < 0) ? (c += 2 * x * ((abs(t) + 1) >> 1)) : 0;
        ans = min(ans, c);
    }
    cout << ans << "\n";
    return 0;
}
</details>

### T7
洛谷 P3471 POC-Trains

首先哈希,每次修改暴力重构哈希值,然后将所有可能的哈希值进行离散化。对于每个字符串,可以知道其在每段时间内的哈希值。对于每种哈希值,也可以知道在每段时间内有多少字符串等于它。所以对每个字符串枚举其所有哈希值,然后在这个哈希值的时间线中找出这个字符串为这个哈希值的时间区间,然后求一个区间最大值即可。

<details>
<summary> 代码 </summary>

```cpp
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
// #define int long long
using namespace std;
// const int P = 1000000007;
const int P = 1610612741;
const int B = 2333;
int n, l, m;
vector<pair<int, int> > vec[1005];
vector<pair<int, int> > st[400005];
int sz[400005];
string str[1005];
int h[1005];
int qa[100005], qb[100005], qc[100005], qd[100005];
map<int, int> mp;
int ra[100005], rb[100005];
int cs[100005];
struct Query {
    int id, l, r;
};
vector<Query> q[400005];
int cnt;
int lg2[400005];
int f[20][400005];
int query(int l, int r) {
    int k = lg2[r - l + 1];
    return max(f[k][l], f[k][r - (1 << k) + 1]);
}
int dsc[400005], dcnt;
int Search(int x, int y) {
    int l = 0, r = (int)st[x].size() - 1, mid, ans = -1;
    while (l <= r) {
        mid = (l + r) >> 1;
        if (st[x][mid].first <= y) 
            ans = mid, l = mid + 1;
        else 
            r = mid - 1;
    }
    return ans;
}
int ans[400005];
int ih[400005];
signed main() {
    lg2[0] = -1;
    for (int i = 1; i <= 400000; i++) lg2[i] = lg2[i - 1] + ((i & (-i)) == i);
    cin >> n >> l >> m;
    for (int i = 1; i <= n; i++) {
        cin >> str[i];
        str[i] = ' ' + str[i];
        for (int j = 1; j <= l; j++) 
            h[i] = ((1ll * h[i]) * B + str[i][j]) % P;
        dsc[++dcnt] = h[i];
        ih[i] = h[i];
    }
    for (int i = 1; i <= n; i++) vec[i].emplace_back(make_pair(0, h[i]));
    for (int i = 1; i <= m; i++) {
        int &a = qa[i], &b = qb[i], &c = qc[i], &d = qd[i];
        cin >> a >> b >> c >> d;
        h[a] = 0;
        swap(str[a][b], str[c][d]);
        for (int j = 1; j <= l; j++) h[a] = ((1ll * h[a]) * B + str[a][j]) % P;
        h[c] = 0;
        for (int j = 1; j <= l; j++) h[c] = ((1ll * h[c]) * B + str[c][j]) % P;
        dsc[++dcnt] = h[a], dsc[++dcnt] = h[c];
        vec[a].emplace_back(make_pair(i, h[a]));
        ra[i] = (int)vec[a].size() - 1;
        vec[c].emplace_back(make_pair(i, h[c]));
        rb[i] = (int)vec[c].size() - 1;
    }
    sort(dsc + 1, dsc + dcnt + 1);
    for (int i = 1; i <= dcnt; i++) mp[dsc[i]] = (mp[dsc[i - 1]] + (dsc[i] != dsc[i - 1]));
    cnt = mp[dsc[dcnt]];
    for (int i = 1; i <= n; i++) cs[mp[ih[i]]]++;
    for (int i = 1; i <= cnt; i++) st[i].emplace_back(make_pair(0, cs[i]));
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < (int)vec[i].size(); j++) 
            vec[i][j].second = mp[vec[i][j].second];
        vec[i].emplace_back(make_pair(m + 1, 0));
    }
    for (int i = 1; i <= m; i++) {
        int a = qa[i], c = qc[i];
        int oa, ca, ob, cb;
        oa = vec[a][ra[i] - 1].second, ca = vec[a][ra[i]].second;
        ob = vec[c][rb[i] - 1].second, cb = vec[c][rb[i]].second;
        cs[oa]--, cs[ca]++;
        cs[ob]--, cs[cb]++;
        st[oa].emplace_back(make_pair(i, cs[oa]));
        st[ca].emplace_back(make_pair(i, cs[ca]));
        st[ob].emplace_back(make_pair(i, cs[ob]));
        st[cb].emplace_back(make_pair(i, cs[cb]));
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < (int)vec[i].size() - 1; j++) {
            int ta = vec[i][j].first, tb = vec[i][j + 1].first, tc = vec[i][j].second;
            q[tc].emplace_back((Query) { i, Search(tc, ta), Search(tc, tb - 1) });
        }
    }
    for (int i = 1; i <= cnt; i++) {
        int cn = st[i].size();
        for (int j = 0; j < (int)st[i].size(); j++) f[0][j + 1] = st[i][j].second;
        for (int j = 1; (1 << j) <= cn; j++) {
            for (int k = 1; k + (1 << j) - 1 <= cn; k++) 
                f[j][k] = max(f[j - 1][k], f[j - 1][k + (1 << (j - 1))]);
        }
        for (auto v : q[i]) 
            ans[v.id] = max(ans[v.id], query(v.l + 1, v.r + 1));
    }
    for (int i = 1; i <= n; i++) cout << ans[i] << "\n";
    return 0;
}

T8

洛谷 P3472 MAF-Mafia

发现是基环树。首先考虑死最多的情况,对于一个没有其他边连向它的单独的环,最优的情况是只活一个。对于一棵基环树,所有叶子不会死,其他人都会死。这是容易构造的。然后对于死最少得情况,我们拓扑排序,先把所有叶子扔进去,然后每个点把他指向的点指向的点的度数减一,度数为 \(0\) 了就扔进队列,代表这个人活了。如果某个环之外的人没有入队,那说明是死了。接下来考虑环。这样操作完之后有的环可能被拆掉了,有的环还是好好的。被拆掉的就不管了,反正已经算过了。对于没有拆掉的环,死最少的情况是死 \(\lfloor \frac{sz}{2} \rfloor\) 个人。然后就没了。

代码
#include <iostream>
#include <queue>
using namespace std;
int p[1000005];
int n;
int in[1000005];
bool die[1000005];
bool live[1000005];
int mn = 0, mx = 0; // mn live, mx live
queue<int> q;
int main () {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> p[i], in[p[i]]++;
    for (int i = 1; i <= n; i++) {
        if (!in[i]) {
            ++mn;
            ++mx;
            q.push(i);
        }
    }
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        if (die[p[x]]) 
            continue;
        die[p[x]] = 1;
        live[p[p[x]]] = 1;
        if (--in[p[p[x]]] == 0) {
            ++mx;
            q.push(p[p[x]]);
        }
    }
    for (int i = 1; i <= n; i++) {
        if (in[i] && !die[i]) {
            int len = 0;
            bool flag = 0;
            for (int j = i; !die[j]; j = p[j]) {
                flag |= live[j];
                die[j] = 1;
                ++len;
            }
            mx += len / 2;
            mn += (!flag && len > 1);
        }
    }
    cout << n - mx << " " << n - mn << "\n";
    return 0;
}

T10

洛谷 P3474 KUP-Plot Purchase

首先大于 \(2k\) 的不能选,\([k, 2k]\) 中的格子直接就是答案,所以只需要考虑小于 \(k\) 的格子。相当于一个矩形,有一些不能选的点,要求选出一个子矩形使得和在 \([k, 2k]\),保证每个点权值 \(\in [0, k)\)。可以发现只要最大权子矩形权值大于 \(2k\) 就必然存在符合要求的矩形。我们先找到这个最大权子矩形,然后一行一行地去削它。对于当前消去的行,如果权值大于 \(2k\),那接下来就同理来削这个行。如果权值在 \([k, 2k]\),就找到了答案。否则直接消掉。这样最终得到的矩形一定在 \([k, 2k]\) 之间,因为一个大于 \(2k\) 的数每次减去一个小于 \(k\) 的数,其取值不可能跳过 \([k, 2k]\) 这个区间。所以一定能找到。

代码
#include <iostream>
#include <cassert>
#define int long long
using namespace std;
int k, n;
int a[2005][2005];
int up[2005][2005];
int S[2005][2005];
inline int Sum(int x1, int y1, int x2, int y2) {
    x1 > x2 ? swap(x1, x2) : void();
    y1 > y2 ? swap(y1, y2) : void();
    return (S[x2][y2] - S[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 - 1][y1 - 1]);
}
int val[2005];
int tol[2005], tor[2005];
pair<int, int> stk[2005];
int sz;
int curans;
int ax1, ax2, ay1, ay2;
void work() {
    sz = 0;
    stk[sz].first = 0;
    for (int i = 1; i <= n; i++) {
        while (sz && stk[sz].second >= val[i]) --sz;
        tol[i] = stk[sz].first + 1;
        stk[++sz] = make_pair(i, val[i]);
    }
    sz = 0;
    stk[sz].first = n + 1;
    for (int i = n; i; i--) {
        while (sz && stk[sz].second >= val[i]) --sz;
        tor[i] = stk[sz].first - 1;
        stk[++sz] = make_pair(i, val[i]);
    }
}
signed main() {
    cin >> k >> n;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cin >> a[i][j];
            if (k <= a[i][j] && a[i][j] <= (k << 1)) {
                cout << j << " " << i << " " << j << " " << i << "\n";
                return 0;
            }
            S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + a[i][j];
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) 
            up[i][j] = (a[i][j] > (k << 1) ? i : up[i - 1][j]);
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) val[j] = i - up[i][j];
        work();
        for (int j = 1, t; j <= n; j++) {
            if (a[i][j] > (k << 1)) 
                continue;
            if ((t = Sum(up[i][j] + 1, tol[j], i, tor[j])) > curans) {
                curans = t;
                ax1 = up[i][j] + 1, ax2 = i, ay1 = tol[j], ay2 = tor[j];
            }
        }
    }
    if (curans < k) {
        cout << "NIE\n";
        return 0;
    }
    while (Sum(ax1, ay1, ax2, ay2) > (k << 1) && ax1 < ax2) {
        int tmp = Sum(ax1, ay1, ax1, ay2);
        if (tmp >= k) {
            ax2 = ax1;
            break;
        } else 
            ++ax1;
    }
    while (Sum(ax1, ay1, ax2, ay2) > (k << 1)) ++ay1;
    cout << ay1 << " " << ax1 << " " << ay2 << " " << ax2 << "\n";
    return 0;
}

拓扑排序,分类讨论的思想。

posted @ 2024-04-11 21:32  forgotmyhandle  阅读(1)  评论(0编辑  收藏  举报