「杂题集」- 2019年9月19日

方格取数

看一眼复杂度,\(O(nm)\)级别的,考虑两个循环的 DP。分析整个题面之后发现,我们穿过边界时,当且仅当另一个连通块的权值比当前的大,否则便不会为了穿过边界而损失当前金块。所以,我们做一个 DP 一样的东西:因为发现每个格子只能走一遍,那么在每一列你只能选择一直向上走和一直向下走,所以这个 DP 便没了后效性,然后再处理连通块之间的连接判断就做完了。

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

using namespace std;

const int MAX_N = 1030;

int mp[MAX_N][MAX_N], n, m, T;
ll dp[MAX_N][MAX_N];

int main()
{
    while (scanf("%d%d", &n, &m) != EOF)
    {
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                scanf("%d", &mp[i][j]);
        for (int j = 1; j <= m; j++)
        {
            ll cur = -1e18;
            for (int i = 1; i <= n; i++)
                if (mp[i][j] == -1)
                    cur = -1e18, dp[i][j] = cur;
                else
                    cur = max(cur, dp[i][j - 1]) + mp[i][j], dp[i][j] = cur;
            cur = -1e18;
            for (int i = n; i >= 1; i--)
                if (mp[i][j] == -1)
                    cur = -1e18;
                else
                    cur = max(cur, dp[i][j - 1]) + mp[i][j], dp[i][j] = max(cur, dp[i][j]);
            int down_bound = 0;
            for (int i = n; i >= 1; i--)
                if (mp[i][j] == -1)
                    break;
                else if (dp[i][j - 1] >= 0)
                {
                    down_bound = i;
                    break;
                }
            cur = 0;
            for (int i = 1; i < down_bound; i++)
                if (mp[i][j] == -1)
                    break;
                else
                    cur += mp[i][j], dp[i][j] = max(dp[i][j], cur);
            int up_bound = n + 1;
            for (int i = 1; i <= n; i++)
                if (mp[i][j] == -1)
                    break;
                else if (dp[i][j - 1] >= 0)
                {
                    up_bound = i;
                    break;
                }
            cur = 0;
            for (int i = n; i > up_bound; i--)
                if (mp[i][j] == -1)
                    break;
                else
                    cur += mp[i][j], dp[i][j] = max(dp[i][j], cur);
        }
        ll ans = -1;
        for (int i = 1; i <= n; i++)
            ans = max(ans, dp[i][m]);
        printf("%lld\n", ans);
    }
    return 0;
}

染色

真的是您🐎的签到题。

首先,我们可以枚举有\(i\)个\(A\),然后判断剩下的是否整除\(B\)。如果整除,那么就可以加入答案,考虑计算部分贡献,在\(n\)个砖块里选择\(i\)个作为\(A\)的,剩下再乘上\(n\)个砖块里面选择\(\frac{n – iA}{B}\)个,如果重叠的就算做是\(C\),如果非重叠就是\(B\)。

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

using namespace std;

const int MAX_N = 3e5 + 200, mod = 998244353;

int fac[MAX_N], fac_inv[MAX_N], T;
ll n, A, B, x;

int quick_pow(int bas, int tim)
{
    int ans = 1;
    while (tim)
    {
        if (tim & 1)
            ans = 1LL * ans * bas % mod;
        bas = 1LL * bas * bas % mod;
        tim >>= 1;
    }
    return ans;
}

int combinator(int n_, int k_)
{
    if (n_ < k_ || n_ < 0 || k_ < 0)
        return 0;
    return 1LL * fac[n_] * fac_inv[k_] % mod * fac_inv[n_ - k_] % mod;
}

int main()
{
    scanf("%d", &T);
    for (int i = fac[0] = 1; i < MAX_N; i++)
        fac[i] = 1LL * fac[i - 1] * i % mod;
    fac_inv[MAX_N - 1] = quick_pow(fac[MAX_N - 1], mod - 2);
    for (int i = MAX_N - 2; i >= 0; i--)
        fac_inv[i] = 1LL * fac_inv[i + 1] * (i + 1) % mod;
    while (T--)
    {
        scanf("%lld%lld%lld%lld", &n, &A, &B, &x);
        ll res = 0, ans = 0;
        for (int i = 0; i <= n; i++)
        {
            ll tmp = x - 1LL * i * A;
            if (tmp > 0 && (tmp % B == 0))
                ans = (1LL * ans + 1LL * combinator(n, i) * combinator(n, tmp / B) % mod) % mod;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

字符串

这道题其实比较好想,思考计算子序列的递推公式:

\[ dp[i] = 2dp[i – 1] – dp[lastPos[str[i]]] \]

先计算前缀的子序列个数,然后在考虑向后构造:我们发现,DP 数组是由单调递增的性质的,那么我们每次转移的时候取最小的\(lastPos[char]\)即可。

// C.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_K = 27, mod = 1e9 + 7;

int dp[MAX_K], n, m, k, prev_pos[MAX_K], ans;

int main()
{
    scanf("%d %d\n", &m, &k);
    int ans = 1, x, old;
    char ch;
    while (true)
    {
        ch = getchar(), x = ch - 'a' + 1;
        if (x < 1 || x > 26)
            break;
        old = ans;
        ans = 2LL * ans % mod, ans = (1LL * ans + mod - dp[x]) % mod;
        dp[x] = old, prev_pos[x] = ++n;
    }
    for (int i = 1; i <= m; i++)
    {
        x = 0;
        for (int j = 1; j <= k; j++)
            if (!x || prev_pos[x] > prev_pos[j])
                x = j;
        prev_pos[x] = ++n, old = ans;
        ans = 2LL * ans % mod, ans = (ans + mod - dp[x]) % mod;
        dp[x] = old;
    }
    printf("%d", ans);
    return 0;
}

膜方俱乐部

sb 基环树,记录下环的值还有链上的值,然后就可以愉快的 AC 了。

// B.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 200100;

int head[MAX_N], current, deg[MAX_N], n, val[MAX_N], deletion[MAX_N], mem[MAX_N], sum[MAX_N], loop[MAX_N];

struct edge
{
    int to, nxt;
} edges[MAX_N << 1];

int find(int x) { return x == mem[x] ? x : mem[x] = find(mem[x]); }

void addpath(int src, int dst)
{
    edges[current].to = dst, edges[current].nxt = head[src];
    head[src] = current++;
}

void toposort()
{
    queue<int> q;
    for (int i = 1; i <= n; i++)
        if (deg[i] == 0)
            q.push(i);
    while (!q.empty())
    {
        int u = q.front();
        q.pop(), deg[u]--;
        for (int i = head[u]; i != -1; i = edges[i].nxt)
            if (--deg[edges[i].to] == 0)
                q.push(edges[i].to);
    }
    for (int i = 1; i <= n; i++)
        if (deg[i] > 0)
            loop[find(i)] += val[i];
}

void dfs(int u, int fat)
{
    if (deg[fat] <= 0)
        val[u] += val[fat];
    for (int i = head[u]; i != -1; i = edges[i].nxt)
        if (edges[i].to != fat && deg[edges[i].to] <= 0)
            dfs(edges[i].to, u);
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &val[i]), mem[i] = i, sum[i] = val[i];
    for (int i = 1, v; i <= n; i++)
    {
        scanf("%d", &v), addpath(i, v), addpath(v, i), deg[v]++;
        int x = find(i), y = find(v);
        if (x != y)
            mem[y] = x, sum[x] += sum[y];
    }
    toposort();
    for (int i = 1; i <= n; i++)
        if (deg[i] > 0)
            dfs(i, 0);
    for (int i = 1; i <= n; i++)
        if (deg[i] > 0)
            printf("%d\n", loop[find(i)]);
        else
            printf("%d\n", val[i] + loop[find(i)]);
    return 0;
}

排名

为什么可以这么简单啊?

// A.cpp
#include <bits/stdc++.h>

using namespace std;

int n, m, prefix, suffix;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1, p; i <= n; i++)
        scanf("%d", &p), prefix += p - 1, suffix += m - p;
    printf("%d\n%d\n", max(1, m - suffix), min(m, prefix + 1));
    return 0;
}

或者过于完美亦未可知

这道题就是数据结构和思维结合的题目。题目求的是:怎样安排\(q\)个操作,使得最后的串与目标串相差最小。

首先,把答案定为目标串中\(0\)的个数,假定整个序列完全覆盖了\(1\)。我们要求的答案就是尽量把这个答案减干净(也就是求最小值)。所以,对于每一个操作,我们都在他的左端点和右端点上打上一个标记,来标注最少能少多少个,然后这道题就可以解决了(有点像最小割的方法)。

// C.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 800010, INF = 0x3f3f3f3f;

int min_val[MAX_N], seq[MAX_N], n, q, ans;
vector<int> queries[MAX_N];

#define mid ((l + r) >> 1)
#define lson (p << 1)
#define rson ((p << 1) | 1)

void pushup(int p)
{
    min_val[p] = min(min_val[lson], min_val[rson]);
}

void build(int l, int r, int p)
{
    if (l == r)
        return (void)(min_val[p] = ((l == 0) ? 0 : INF));
    build(l, mid, lson), build(mid + 1, r, rson);
    pushup(p);
}

void update(int qx, int l, int r, int p, int val)
{
    if (l == r)
        return (void)(min_val[p] = min(min_val[p], val));
    if (qx <= mid)
        update(qx, l, mid, lson, val);
    else
        update(qx, mid + 1, r, rson, val);
    pushup(p);
}

int query(int ql, int qr, int l, int r, int p)
{
    if (ql <= l && r <= qr)
        return min_val[p];
    int ret = INF;
    if (ql <= mid)
        ret = min(ret, query(ql, qr, l, mid, lson));
    if (mid < qr)
        ret = min(ret, query(ql, qr, mid + 1, r, rson));
    return ret;
}

#undef mid
#undef lson
#undef rson

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        scanf("%d", &seq[i]), ans += !seq[i];
    scanf("%d", &q);
    for (int i = 1, x, y; i <= q; i++)
        scanf("%d%d", &x, &y), queries[x - 1].push_back(y);
    build(0, n, 1);
    for (int i = 0; i < n; i++)
    {
        for (int j = 0, siz = queries[i].size(); j < siz; j++)
            update(queries[i][j], 0, n, 1, query(i, queries[i][j], 0, n, 1));
        update(i + 1, 0, n, 1, query(i, i, 0, n, 1) + (seq[i] ? 1 : -1));
    }
    printf("%d\n", ans + query(n, n, 0, n, 1));
    return 0;
}

献给许许多多的忌日

先求一遍树上最大独立集,然后计算一下:如果独立集的两倍都小于\(n\),那么对于警察来讲可以建造更多无法起火的房屋;如果独立集大于\(k\),那么断边也行不通;

// A.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 5050;

int head[MAX_N], current, n, k, dp[MAX_N][2];

struct edge
{
    int to, nxt;
} edges[MAX_N << 1];

void addpath(int src, int dst)
{
    edges[current].to = dst, edges[current].nxt = head[src];
    head[src] = current++;
}

void dfs(int u, int fa)
{
    for (int i = head[u]; i != -1; i = edges[i].nxt)
        if (edges[i].to != fa)
            dfs(edges[i].to, u), dp[u][0] += max(dp[edges[i].to][1], dp[edges[i].to][0]);
    for (int i = head[u]; i != -1; i = edges[i].nxt)
        if (edges[i].to != fa)
            dp[u][1] = max(dp[u][1], dp[u][0] - max(dp[edges[i].to][1], dp[edges[i].to][0]) + dp[edges[i].to][0] + 1);
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d%d", &n, &k);
    for (int i = 1, u, v; i <= n - 1; i++)
        scanf("%d%d", &u, &v), addpath(u, v), addpath(v, u);
    dfs(1, 0);
    int independent_set = max(dp[1][0], dp[1][1]);
    if ((independent_set << 1) < n || k < independent_set - 1)
        puts("policeman");
    else
        puts("arsonist");
    return 0;
}

Leave a Reply

Your email address will not be published. Required fields are marked *