首页 > 其他分享 >20240503

20240503

时间:2024-05-04 10:55:23浏览次数:22  
标签:5005 int ecnt ++ 20240503 str dp

T1

NFLSOJ P2023050961 捉迷藏

首先只需要考虑所有叶子,只要每个叶子都连向了另一个距离超过 \(2\) 的叶子,则符合要求。距离超过 \(2\) 等价于在不同的父亲下。则问题变为一堆点,每个点有颜色,同色点间没有边,异色点间两两有边,求最大匹配。

结论:设点最多的颜色 \(c\) 有 \(x\) 个点,若 \(x \ge \lceil \frac{n}{2} \rceil\),则答案为 \(n - x\),否则为 \(\lfloor \frac{n}{2} \rfloor\)。前者显然,后者考虑构造。先把这 \(x\) 个点排成一个环,维护一个指针指向环内任意一个点。遍历其他所有点,将这个点插入当前指针所指的点的下一位,然后将指针顺时针移到下一个颜色为 \(c\) 的点。容易发现这样得到的环任意两个相邻点不同色,两两匹配即可。

代码
#include <iostream>
#include <algorithm>
using namespace std;
int n;
int head[200005], nxt[400005], to[400005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int f[200005], dfn[200005];
int lf[200005], lcnt;
int deg[200005];
int ncnt;
void dfs1(int x, int fa) {
    f[x] = fa;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) 
            dfs1(v, x);
    }
}
int main() {
    freopen("hide.in", "r", stdin);
    freopen("hide.out", "w", stdout);
    int tc;
    cin >> tc;
    while (tc--) {
        for (int i = 1; i <= n; i++) deg[i] = head[i] = 0;
        ecnt = 0;
        cin >> n;
        for (int i = 1; i < n; i++) {
            int u, v;
            cin >> u >> v;
            add(u, v);
            add(v, u);
            ++deg[u], ++deg[v];
        }
        if (n == 2) {
            cout << "-1\n";
            continue;
        }
        ncnt = lcnt = 0;
        int rt = 1;
        for (int i = 1; i <= n; i++) (deg[i] == 1) ? (lf[++lcnt] = i) : (rt = i);
        dfs1(rt, 0);
        sort(lf + 1, lf + lcnt + 1, [](int a, int b) { return f[a] < f[b]; });
        if (f[lf[1]] == f[lf[lcnt]]) {
            cout << -1 << "\n";
            continue;
        }
        int ans = 0;
        for (int i = 1, j = 1; i <= lcnt; i = j) {
            while (j <= lcnt && f[lf[j]] == f[lf[i]]) ++j;
            if (j - i >= (lcnt + 1) / 2) {
                ans = j - i;
                break;
            }
        }
        if (!ans) 
            ans = (lcnt + 1) / 2;
        cout << ans << "\n";
    }
    return 0;
}

T2

NFLSOJ P2023050962 涂奶酪

首先注意到所有点会形成一棵基环树,而我们只关心 \(s\) 所在的弱连通分量。先假设 \(s\) 原本是白色,而我们需要花 \(p_s\) 的代价把它凭空染成黄色,最后再减去这个代价。会发现一个点会被多次染色,颜色就在两者间交替。首先考虑 \(s\) 不在环内的情况,考虑 \(dp[i][j]\) 表示 \(i\) 的子树内,\(i\) 被染了 \(j\) 次色的最大收益。容易发现 \(i\) 被染色 \(j\) 次时其所有儿子染色次数都 \(\le j\),所以是好转移的。当 \(s\) 不在环内时,\(j\) 只能为 \(0 / 1 / 2\)。

然后考虑 \(s\) 所在环大小为 \(2\) 的情况。对 \(s\) 和 \(a_s\) 分别跑一次上一段的树形 dp,然后由于环长只有 \(2\),\(a_s\) 至多被染色一次。所以情况数很少,直接分讨即可。

对于剩下的,会发现可能会有一个黄色的线段在环上一直转悠。如果在环上每个点写下这个点的染色次数,会发现任何时刻所有数极差不超过 \(2\),而且任何符合要求的染色次数都可以构造。所以我们先对环上每个点跑那个树形 dp,然后把第二维开到 \(n\),最后枚举一下 \(s\) 的染色次数,在环上稍微 dp 一下即可。

代码
#include <iostream>
#define int long long
using namespace std;
const int inf = 0x7ffffffffffffff;
int n, s;
int a[5005];
int dp[5005][5005];
int c[5005], ccnt;
int head[5005], nxt[5005], to[5005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
bool vis[5005];
int w[5005], p[5005];
void dfs(int x) {
    for (int i = 1; i <= n; i++) dp[x][i] = ((i & 1) ? w[x] : 0) - p[x] * i;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (!vis[v]) {
            dfs(v);
            int mx = 0;
            for (int j = 1; j <= n; j++) {
                mx = max(mx, dp[v][j]);
                dp[x][j] = max(dp[x][j], dp[x][j] + mx);
            }
        }
    }
}
signed main() {
    freopen("cheese.in", "r", stdin);
    freopen("cheese.out", "w", stdout);
    cin >> n >> s;
    for (int i = 1; i <= n; i++) cin >> w[i];
    for (int i = 1; i <= n; i++) cin >> p[i];
    for (int i = 1; i <= n; i++) cin >> a[i], add(a[i], i);
    for (int i = s; !vis[i]; i = a[i]) {
        vis[i] = 1;
        c[++ccnt] = i;
    }
    if (a[c[ccnt]] != s) {
        dfs(s);
        cout << max(dp[s][1], dp[s][2]) + p[s] << "\n";
        return 0;
    }
    if (ccnt == 2) {
        dfs(s);
        dfs(a[s]);
        cout << max(dp[s][1], max(dp[s][1] + dp[a[s]][1], dp[s][2])) + p[s] << "\n";
        return 0;
    }
    for (int i = 1; i <= ccnt; i++) dfs(c[i]);
    int ans = -inf;
    for (int i = 1; i <= n; i++) {
        int s0, s1, s2;
        s0 = s1 = s2 = dp[s][i];
        for (int j = ccnt; j > 1; j--) {
            (i ^ 1) ? (s2 += dp[c[j]][i - 2]) : 0;
            s1 += dp[c[j]][i - 1];
            s0 += dp[c[j]][i];
            s1 = max(s1, s0);
            s2 = max(s2, s1);
        }
        ans = max(ans, (i ^ 1) ? s2 : s1);
    }
    cout << ans + p[s] << "\n";
    return 0;
}

T3

NFLSOJ P2023050963 玩游戏

先假设 \(0\) 比 \(1\) 少,否则翻转序列并反转 \(0 / 1\)。通过模拟可以发现长度大于 \(1\) 的 \(1\) 连续段会向右移,而 \(0\) 连续段会向左移,当两个相撞的时候每次两个的长度都会减少 \(1\) 直到其中一个长度为 \(1\) 为止。我们先找到一个位置使得将 \(0\) 视为 \(-1\) 之后所有前缀和非负,并且序列末尾没有 \(1\)。可以发现一定可以找到这样的位置。然后按照连续段模拟,每找到一个 \(1\) 连续段就扔到栈里,找到一个 \(0\) 连续段就跟栈里的东西撞一撞,抵消掉。最后可以知道剩余的 \(1\) 连续段的位置和长度。对于剩下的位置 \(01\) 交替即可。然后就是一个 KMP 求循环节。把最后一次相撞的结束时间和循环节加起来即为所求。

代码
#include <iostream>
#include <algorithm>
#include <cassert>
#define int long long
using namespace std;
const int P = 1000000007;
string str;
int n;
struct node {
    int s, l;
} stk[10000005];
int sz;
int nxt[10000005];
int ss[10000005];
signed main() {
    freopen("game.in", "r", stdin);
    freopen("game.out", "w", stdout);
    int tc;
    cin >> tc;
    while (tc--) {
        cin >> str;
        n = str.size();
        int c0 = 0, c1 = 0;
        for (int i = 0; i < n; i++) c0 += (str[i] == '0'), c1 += (str[i] == '1');
        if (c0 > c1) {
            for (int i = 0; i < n; i++) str[i] = '0' + ((str[i] - '0') ^ 1);
            reverse(str.begin(), str.end());
        }
        int s = 0, p = 0, cs = 1;
        for (int i = 0; i < n; i++) {
            s += (str[i] == '0' ? -1 : 1);
            s < cs ? (p = i + 1, cs = s) : 0;
        }
        if (s == n) {
            cout << 1 << "\n";
            continue;
        }
        if (p && p != n) {
            string tstr = str;
            str = "";
            for (int i = p; ; i = (i + 1) % n) {
                str += tstr[i];
                if (i == p - 1) 
                    break;
            }
        }
        int pos = n - 1;
        while (str[pos] == '1') str.pop_back(), --pos;
        string asdf;
        while (pos + 1 < n) asdf += '1', ++pos;
        str = asdf + str;
        sz = 0;
        int ans = 0;
        for (int i = 0, j = 0; i < n; i = j) {
            while (j < n && str[j] == str[i]) ++j;
            if (j == i + 1) 
                continue;
            if (str[i] == '1') 
                stk[++sz] = (node) { i, j - i - 1 };
            else {
                int cl = j - i - 1;
                while (cl) {
                    int val = min(cl, stk[sz].l);
                    ans = max(ans, (j - stk[sz].s + min(cl - stk[sz].l, stk[sz].l - cl)) / 2 - 1);
                    stk[sz].l -= val, cl -= val;
                    (stk[sz].l == 0) ? (--sz) : 0;
                }
            }
        }
        for (int i = 1; i <= n; i++) ss[i] = 0;
        int cur = 1;
        stk[++sz] = (node) { n, -1 };
        for (int i = 1; i <= sz; i++) {
            stk[i].s += ans;
            while (cur < stk[i].s + 1) ss[(cur - 1) % n + 1] = ((cur & 1) != (stk[i].s & 1)), ++cur;
            for (int j = 0; j <= stk[i].l; j++) 
                ss[(stk[i].s + j) % n + 1] = 1;
            cur = stk[i].s + stk[i].l + 1 + 1;
        }
        nxt[0] = -1, nxt[1] = 0;
        for (int i = 2, j = 1; i <= n; i++, j++) {
            while (j && ss[i] != ss[j]) j = nxt[j - 1] + 1;
            nxt[i] = j;
        }
        ans += (n % (n - nxt[n]) == 0 ? n - nxt[n] : n);
        cout << ans % P << "\n";
    }
    return 0;
}

经典结论。

观察性质。

按照题意模拟,观察性质。

标签:5005,int,ecnt,++,20240503,str,dp
From: https://www.cnblogs.com/forgotmyhandle/p/18172100

相关文章

  • 20240503比赛总结
    T1[CF1279C]StackofPresentshttps://gxyzoj.com/d/hzoj/p/3686数据出锅了,100->40按题意模拟即可,可以发现,最优情况下,一定是将取出的数按后面的拿的顺序排序,O(1)取出,而在取之前未排序的,则需要花2k+1的时间排序并取出代码:#include<cstdio>#definelllonglongusingnamesp......