POI-2

T1

洛谷 P3436 PRO-Professor Szu

首先缩点。然后从所有没有入度的强连通分量开始 dfs,进行 dp,数一下每个点到终点有多少路径。要注意的是当且仅当一个点能够到达终点时才能够用来更新其他点的 dp 值。

代码
#include <iostream>
#define int long long
using namespace std;
int n, m;
struct Graph {
    int head[1000005], nxt[1000005], to[1000005], ecnt;
    void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
} g1, g2;
int clr[1000005], scnt, ncnt;
int dfn[1000005], low[1000005], stk[1000005], sz;
bool vis[1000005];
int ssz[1000005];
void tarjan(int x) {
    dfn[x] = low[x] = ++ncnt;
    stk[++sz] = x;
    vis[x] = 1;
    for (int i = g1.head[x]; i; i = g1.nxt[i]) {
        int v = g1.to[i];
        if (!dfn[v]) {
            tarjan(v);
            low[x] = min(low[x], low[v]);
        } else if (vis[v]) 
            low[x] = min(low[x], dfn[v]);
    }
    if (low[x] == dfn[x]) {
        ++scnt;
        int t;
        do {
            t = stk[sz--];
            vis[t] = 0;
            clr[t] = scnt;
            ++ssz[scnt];
        } while (t != x);
    }
}
int in[1000005];
bool sl[1000005];
bool lp[1000005];
int dest;
int dp[1000005];
bool tol[1000005];
bool tod[1000005];
void dfs(int x) {
    if (vis[x]) 
        return;
    vis[x] = 1;
    tol[x] |= lp[x];
    for (int i = g2.head[x]; i; i = g2.nxt[i]) {
        int v = g2.to[i];
        dfs(v);
        tod[x] |= tod[v];
        if (tod[v]) {
            dp[x] += dp[v];
            tol[x] |= tol[v];
            dp[x] = min(dp[x], 36501ll);
        }
    }
}
signed main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        g1.add(u, v);
        if (u == v) 
            sl[u] = 1;
    }
    for (int i = 1; i <= n + 1; i++) {
        if (!dfn[i]) 
            tarjan(i);
    }
    dp[dest = clr[n + 1]] = 1;
    tod[dest] = 1;
    for (int i = 1; i <= n + 1; i++) {
        for (int j = g1.head[i]; j; j = g1.nxt[j]) {
            int v = g1.to[j];
            if (clr[i] != clr[v]) 
                g2.add(clr[i], clr[v]), in[clr[v]]++;
        }
    }
    for (int i = 1; i <= n + 1; i++) lp[clr[i]] |= sl[i];
    for (int i = 1; i <= scnt; i++) lp[i] |= (ssz[i] > 1);
    for (int i = 1; i <= scnt; i++) {
        if (in[i] == 0) 
            dfs(i);
    }
    int ans = -1, acnt = 0;
    for (int i = 1; i <= scnt; i++) {
        if (dp[i]) {
            if (tol[i] || dp[i] > 36500) 
                dp[i] = 36501;
            if (dp[i] > ans) 
                ans = dp[i], acnt = 0;
            if (dp[i] == ans) 
                acnt += ssz[i] - (dest == i);
        }
    }
    if (ans > 36500) 
        cout << "zawsze\n";
    else 
        cout << ans << "\n";
    cout << acnt << "\n";
    for (int i = 1; i <= n; i++) {
        if (dp[clr[i]] == ans) 
            cout << i << " ";
    }
    cout << "\n";
    return 0;
}

T2

洛谷 P3438 ZAB-Frogs

首先二分答案,check 时 bfs,然后就要求每个点是否会被某个圆覆盖。可以发现一个圆在每一列上覆盖的都是一条线段,而且中点在圆心所在横线上。又可以观察到圆心横坐标离当前列越近,则覆盖的线段越长,并且能够覆盖相同圆心纵坐标的圆所覆盖的线段。因此只需要对每列上的每个点求出在纵坐标与之相等且横坐标最近的圆心即可。每次二分一个距离(的平方),就对每个点都算一下它给所在列贡献了哪些被覆盖的点。差分一下即可。有一个细节就是这样做会使得原本距离正好为二分的距离的点被封掉不能走,所以这样求出的答案会比标准答案小 \(1\)。输出时加上即可。

代码
#include <iostream>
#include <queue>
#define int long long
using namespace std;
int sx, sy, tx, ty;
int n, m, X;
bool bad[1005][1005];
int a[1005][1005];
int b[1005][1005];
int d[1005][1005];
int s[1005][1005];
bool vis[1005][1005];
queue<pair<int, int> > q;
int dx[4] = { 1, 0, -1, 0 };
int dy[4] = { 0, 1, 0, -1 };
int srt[5000005];
bool bfs() {
    if (s[sx][sy]) 
        return 0;
    if (sx == tx && sy == ty) 
        return 1;
    while (!q.empty()) q.pop();
    q.push(make_pair(sx, sy));
    s[sx][sy] = 1;
    while (!q.empty()) {
        pair<int, int> tmp = q.front();
        q.pop();
        for (int i = 0; i < 4; i++) {
            int xx = tmp.first + dx[i], yy = tmp.second + dy[i];
            if (xx && yy && xx <= n && yy <= m && !s[xx][yy]) {
                if (xx == tx && yy == ty) 
                    return 1;
                s[xx][yy] = 1;
                q.push(make_pair(xx, yy));
            }
        }
    }
    return 0;
}
bool chk(int k) {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) 
            s[i][j] = 0;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (d[i][j] * d[i][j] > k) 
                continue;
            int t = k - d[i][j] * d[i][j];
            t = srt[t];
            s[max(1ll, i - t)][j]++;
            s[min(n + 1, i + t + 1)][j]--;
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) 
            s[i][j] += s[i - 1][j];
    }
    return bfs();
}
signed main() {
    cin >> n >> m;
    cin >> sx >> sy >> tx >> ty;
    cin >> X;
    for (int i = 1; i * i <= 5000000; i++) {
        for (int j = i * i; j < min(5000000ll, (i + 1ll) * (i + 1)); j++) 
            srt[j] = i;
    }
    for (int i = 1; i <= X; i++) {
        int x, y;
        cin >> x >> y;
        bad[x][y] = 1;
    }
    for (int i = 1; i <= n; i++) {
        int lst = -10000;
        for (int j = 1; j <= m; j++) {
            if (bad[i][j]) 
                lst = j;
            a[i][j] = lst;
        }
        lst = n + 10000;
        for (int j = m; j; j--) {
            if (bad[i][j]) 
                lst = j;
            b[i][j] = lst;
            d[i][j] = min(j - a[i][j], b[i][j] - j);
        }
    }
    int l = 0, r = 5000000, mid, ans = -1;
    while (l <= r) {
        mid = (l + r) >> 1;
        if (chk(mid)) 
            l = mid + 1, ans = mid;
        else 
            r = mid - 1;
    }
    cout << ans + 1 << "\n";
    return 0;
}

T3

洛谷 P3439 MAG-Warehouse

首先切比雪夫转曼哈顿,将两维独立开。对每一维,发现答案选的点不在端点上上肯定不优,因为可以往左或往右移使得总答案变小。所以枚举一下所有点,求一个最小总距离。然后由于直接使用 double 算实在是有可能掉精度,所以先把所有点坐标乘以 \(2\),算出来的点在最后的时候除以 \(2\),然后波动 \(\pm 1\),就可以求出最小的点了。

切比雪夫距离转曼哈顿距离:

\((x, y)\) 变成 \((\frac{x + y}{2}, \frac{x - y}{2})\),这样变换之后两点间的曼哈顿距离就是原本的切比雪夫距离。

代码
#include <iostream>
#include <algorithm>
#include <string.h>
#define abs(x) ((x) > 0 ? (x) : (-(x)))
#define int long long
using namespace std;
int n;
int xx[100005], yy[100005], t[100005];
pair<long double, int> x[100005], y[100005];
long double pre1[100005];
int pre2[100005];
int p1, p2;
long double cur;
int ax, ay;
int calc(int px, int py) {
    int ret = 0;
    for (int i = 1; i <= n; i++) ret += t[i] * max(abs(px - xx[i]), abs(py - yy[i]));
    return ret;
}
signed main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> xx[i] >> yy[i] >> t[i];
        x[i].first = (xx[i] + yy[i]);
        y[i].first = (xx[i] - yy[i]);
        x[i].second = t[i];
        y[i].second = t[i];
    }
    sort(x + 1, x + n + 1);
    sort(y + 1, y + n + 1);
    cur = 0x7fffffffffffffff;
    for (int i = 1; i <= n; i++) pre1[i] = pre1[i - 1] + x[i].first * x[i].second;
    for (int i = 1; i <= n; i++) pre2[i] = pre2[i - 1] + x[i].second;
    for (int i = 1; i <= n; i++) {
        double tmp = x[i].first * pre2[i] - pre1[i] + pre1[n] - pre1[i] - x[i].first * (pre2[n] - pre2[i]);
        if (tmp < cur) 
            cur = tmp, p1 = x[i].first;
    }
    memset(pre1, 0, sizeof pre1);
    memset(pre2, 0, sizeof pre2);
    cur = 0x7fffffffffffffff;
    for (int i = 1; i <= n; i++) pre1[i] = pre1[i - 1] + y[i].first * y[i].second;
    for (int i = 1; i <= n; i++) pre2[i] = pre2[i - 1] + y[i].second;
    for (int i = 1; i <= n; i++) {
        double tmp = y[i].first * pre2[i] - pre1[i] + pre1[n] - pre1[i] - y[i].first * (pre2[n] - pre2[i]);
        if (tmp < cur) 
            cur = tmp, p2 = y[i].first;
    }
    int x1 = (p1 + p2) / 2, y1 = (p1 - p2) / 2;
    cur = 0x7fffffffffffffff;
    for (int i = -1; i <= 1; i++) {
        for (int j = -1; j <= 1; j++) {
            int tmp;
            if ((tmp = calc(x1 + i, y1 + j)) < cur) 
                cur = tmp, ax = x1 + i, ay = y1 + j;
        }
    }
    cout << ax << " " << ay << "\n";
    return 0;
}

T4

洛谷 P3440 SZK-Schools

直接跑费用流即可。

代码
#include <iostream>
#include <queue>
#define abs(x) ((x) > 0 ? (x) : (-(x)))
#define int long long
using namespace std;
const int inf = 2147483647;
int head[2000005], nxt[2000005], to[2000005], res[2000005], ew[2000005], ecnt = 1;
void add(int u, int v, int ww, int c) {
    to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = c, res[ecnt] = ww;
    to[++ecnt] = u, nxt[ecnt] = head[v], head[v] = ecnt, ew[ecnt] = -c, res[ecnt] = 0;
}
int cur[2000005];
int dist[2000005];
int S, T;
queue<int> q;
bool inq[2000005];
bool spfa() {
    for (int i = 1; i <= T; i++) dist[i] = inf;
    dist[S] = 0;
    q.push(S);
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        inq[x] = 0;
        for (int i = head[x]; i; i = nxt[i]) {
            int v = to[i];
            if (dist[v] > dist[x] + ew[i] && res[i] > 0) {
                dist[v] = dist[x] + ew[i];
                if (!inq[v]) {
                    q.push(v);
                    inq[v] = 1;
                }
            }
        }
    }
    return (dist[T] != inf);
}
int cst;
bool vis[2000005];
int dfs(int x, int flow) {
    if (x == T) 
        return flow;
    int ret = 0;
    vis[x] = 1;
    for (int i = cur[x]; i && flow; i = nxt[i]) {
        cur[x] = i;
        int v = to[i];
        if (!vis[v] && dist[v] == dist[x] + ew[i] && res[i] > 0) {
            int tmp = dfs(v, min(flow, res[i]));
            if (tmp) {
                flow -= tmp;
                ret += tmp;
                res[i] -= tmp;
                res[i ^ 1] += tmp;
                cst += tmp * ew[i];
            }
        }
    }
    if (!ret) 
        dist[x] = inf;
    vis[x] = 0;
    return ret;
}
int dinic() {
    int ret = 0;
    while (spfa()) {
        for (int i = 1; i <= T; i++) cur[i] = head[i];
        ret += dfs(S, inf);
    }
    return ret;
}
signed main() {
    int n;
    cin >> n;
    S = n * 2 + 1, T = n * 2 + 2;
    for (int i = 1; i <= n; i++) {
        int m, a, b, k;
        cin >> m >> a >> b >> k;
        for (int j = a; j <= b; j++) 
            add(i, j + n, 1, abs(j - m) * k);
        add(S, i, 1, 0);
    }
    for (int i = 1; i <= n; i++) 
        add(i + n, T, 1, 0);
    if (dinic() == n) 
        cout << cst << "\n";
    else 
        cout << "NIE\n";
    return 0;
}

T5

洛谷 P3441 MET-Subway

所有路径一定是从某一个叶子到另一个叶子。最优的情况是每两个叶子都不重,此时覆盖了 \(l * 2\) 个叶子。然后从叶子往上一层,同样也是最优覆盖 \(l * 2\) 个点。但是可能这一层甚至没有这么多点,所以就是 \(\min \{ 2 \times l, k \}\) 个点,其中 \(k\) 表示当前层点数。使用拓扑排序求出每个点的层数即可。

代码
#include <iostream>
#include <queue>
using namespace std;
int head[1000005], nxt[2000005], to[2000005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int cnt[1000005];
int dist[1000005];
int d[1000005];
queue<int> q;
int main() {
    int n, l;
    cin >> n >> l;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
        d[u]++, d[v]++;
    }
    for (int i = 1; i <= n; i++) {
        if (d[i] == 1) 
            q.push(i), dist[i] = 1;
    }
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        cnt[dist[x]]++;
        for (int i = head[x]; i; i = nxt[i]) {
            int v = to[i];
            if (--d[v] == 1) {
                dist[v] = dist[x] + 1;
                q.push(v);
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) ans += min(cnt[i], l << 1);
    cout << ans << "\n";
    return 0;
}

T6

洛谷 P3444 ORK-Ploughing

首先肯定有一维被完整删去了。我们枚举完整删去了哪一维,接下来肯定是要剩下那一维删去的尽量少。假设所有列都被删去,则我们贪心地先删完列直到不能删了为止时,会面临不知道该先删哪一行的问题。因此我们枚举上面删到哪一行,然后强制上面删到这一行,这样我们就在上面符合要求时先删上面,实在不行了再删下面的行。对于所有行被删去的情况是同理的。

代码
#include <iostream>
#define int long long
using namespace std;
int k, m, n;
int ans = 2147483647;
int a[2005][2005];
int f[2005][2005];
int g[2005][2005];
void solve1(int lim) {
    int l = 1, r = m, u = 1, d = n;
    while (l <= r && u <= d) {
        if (f[d][l] - f[u - 1][l] <= k && l <= r) 
            ++l;
        else if (f[d][r] - f[u - 1][r] <= k && l <= r) 
            --r;
        else if (g[u][r] - g[u][l - 1] <= k && u <= lim && u <= d) 
            ++u;
        else if (g[d][r] - g[d][l - 1] <= k && u <= d) 
            --d;
        else 
            return;
    }
    if (u == lim + 1)
        ans = min(ans, l + m - r + 1 + u + n - d + 1);
}
void solve2(int lim) {
    int l = 1, r = m, u = 1, d = n;
    while (l <= r && u <= d) {
        if (g[u][r] - g[u][l - 1] <= k && u <= d) 
            ++u;
        else if (g[d][r] - g[d][l - 1] <= k && u <= d) 
            --d;
        else if (f[d][l] - f[u - 1][l] <= k && l <= lim && l <= r) 
            ++l;
        else if (f[d][r] - f[u - 1][r] <= k && l <= r) 
            --r;
        else 
            return;
    }
    if (l == lim + 1) 
        ans = min(ans, l + m - r + 1 + u + n - d + 1);
}
signed main() {
    cin >> k >> m >> n;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
            f[i][j] = f[i - 1][j] + a[i][j];
            g[i][j] = g[i][j - 1] + a[i][j];
        }
    }
    for (int i = 0; i <= n; i++) solve1(i);
    for (int i = 0; i <= m; i++) solve2(i);
    cout << ans - 4 << "\n";
    return 0;
}

T8

洛谷 P3446 EST-Aesthetic Text

首先有朴素 dp:\(dp[i][j]\) 表示最后一个句子的最后一个单词是 \(i\),上个句子的最后一个单词是 \(j\) 的最小答案,转移方程 \(dp[i][j] = \min\limits_{0 \le k < j} \{ dp[j][k] + |len(j + 1, i) - len(k + 1, j)| \}\),其中 \(len(i, j)\) 表示 \(i\)\(j\) 这些单词放一个句子里形成的行长度。直接按照方程暴力转移是三次方的,判掉不合法情况即可通过。鉴定为数据太弱。然后来考虑优化。首先看到绝对值很不顺眼,拆掉。根据两个 \(len\) 的大小关系,可以分成两段,当 \(k\) 比较小时是 \(\min \{dp[j][k] + len(k + 1, j) \} - len(j + 1, i)\),另一种是 \(\min \{dp[j][k] - len(k + 1, j) \} + len(j + 1, i)\)。发现这样 \(\min\) 里的东西整个就和 \(i\) 无关了,只与 \(j\) 有关。因此对于每个 \(j\) 算出前缀 \(dp[j][k] + len(k + 1, j)\)\(\min\) 和后缀 \(dp[j][k] - len(k + 1, j)\)\(\min\),转移时只需要求出 \(k\) 使绝对值内东西变号的分界点即可。发现当 \(j\) 单调变化时,\(k\) 的位置也随之单调变化。所以只需要枚举 \(j\) 时同时维护 \(k\) 指针的位置即可做到平方。

代码
#include <iostream>
#include <string.h>
#define int long long
using namespace std;
int dp[2005][2005];
int pre[2005][2005];
int suf[2005][2005];
int S[2005];
inline int len(int i, int j) { return S[j] - S[i - 1] + j - i; }
signed main() {
    int n, lim;
    cin >> lim >> n;
    for (int i = 1; i <= n; i++) cin >> S[i], S[i] += S[i - 1];
    memset(dp, 63, sizeof dp);
    for (int i = 1; i <= n; i++) {
        if (S[i] + i - 1 <= lim) 
            dp[i][0] = 0;
        // for (int j = i - 1; j && S[i] - S[j] + i - j - 1 <= lim; j--) { // 暴力代码
        //     for (int k = j - 1; ~k && S[j] - S[k] + j - k - 1 <= lim; k--) 
        //         dp[i][j] = min(dp[i][j], dp[j][k] + abs(S[i] - S[j] * 2 + S[k] + i - j * 2 + k));
        // }
        for (int j = i - 1, k = j - 1; j && S[i] - S[j] + i - j - 1 <= lim; j--) {
            while (~k && len(k + 1, j) <= lim && S[i] - S[j] + i - j >= S[j] - S[k] + j - k) --k;
            dp[i][j] = min(k == -1 ? 2147483647 : pre[j][k] - len(j + 1, i), suf[j][k + 1] + len(j + 1, i));
        }
        suf[i][i] = 2147483647;
        pre[i][0] = dp[i][0] + len(1, i);
        for (int j = 1; j < i; j++) pre[i][j] = min(pre[i][j - 1], dp[i][j] + len(j + 1, i));
        for (int j = i - 1; ~j; j--) suf[i][j] = min(suf[i][j + 1], dp[i][j] - len(j + 1, i));
    }
    int ans = 2147483647;
    for (int i = 0; i < n; i++) ans = min(ans, dp[n][i]);
    cout << ans << "\n";
    return 0;
}

T9

洛谷 P3447 KRY-Crystals

首先考虑如果有某一个数的大小不被限制,则其他的数都可以在范围内随便乱填,然后通过那个不被限制的数使得所有数的异或和为 \(0\)

接下来把每一位独立开,并且钦定这一位的高位都顶满上界。如果在这一位上有一个 \(m_i\)\(1\),但是填进去的 \(a_i\) 的这一位却为 \(0\),那就说明这个数在以后的位上是自由的。这样别的数在以后的位上可以随便乱填,最后通过这个自由的数来满足以后的位上的异或限制。这样就可以分别对每一位进行 dp。设 \(dp[i][0/1][0/1]\) 表示前 \(i\) 个数,是否已经有自由的数,目前所有数在这一位上的异或和为 \(0 / 1\) 时的方案数。转移时考虑加入一个数 \(x\),分两种情况讨论:

  1. 如果 \(x\) 在当前位 \(t\) 上为 \(0\),则这一位必然填 \(0\),接下来的位也要满足 \(x\) 的限制。

  2. 如果 \(x\) 在当前位 \(t\) 上为 \(1\),则这一位可以填 \(0\),也可以填 \(1\)。如果填 \(1\),则接下来的位也要满足 \(x\) 的限制。如果填 \(0\),则会凭空多出一个自由的数。如果是从一个有自由的数的状态转移而来,则这个数后面的位可以随便乱填。如果是从一个没有自由的数的状态转移而来,则我们在其他的数随便乱填之后拿这个自由的数来满足最后的异或限制,所以只有一种填法。

注意到这个 dp 本质上是在其他的数随便填完之后拿第一个出现的自由的数来满足低位的异或限制,然后通过状态第三维的 \(0\)\(1\) 来满足当前位的异或限制。对于高位,如果枚举到某一位时发现这一位的高位异或起来不为 \(0\),则可以直接退出,因为再往下枚举也不会符合条件。这样就满足了所有位上的异或限制。

注意到我们会把全 \(0\) 算进去,而且不会把所有数都取满限制的情况算进去,所以输出答案的时候要额外搞一下。

代码
#include <iostream>
#include <string.h>
#define int __int128
using namespace std;
long long dp[55][2][2]; // i, whether free, xor sum
unsigned long long ans = 0;
long long a[55], x;
signed main() {
    long long n;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i], x ^= a[i];
    for (signed i = 31; ~i; i--) {
        int tmp = 0;
        memset(dp, 0, sizeof dp);
        dp[0][0][0] = 1;
        int t = (1 << i);
        for (signed j = 1; j <= n; j++) {
            tmp ^= (a[j] >> (i + 1));
            if ((a[j] >> i) & 1) {
                // put 0
                dp[j][1][0] += dp[j - 1][0][0];
                dp[j][1][0] += dp[j - 1][1][0] * t;
                dp[j][1][1] += dp[j - 1][0][1];
                dp[j][1][1] += dp[j - 1][1][1] * t;
                // put 1
                dp[j][1][0] += dp[j - 1][1][1] * (1 + (a[j] & (t - 1))); // 可以填 0,所以要加 1
                dp[j][1][1] += dp[j - 1][1][0] * (1 + (a[j] & (t - 1)));
                dp[j][0][0] += dp[j - 1][0][1] * (1 + (a[j] & (t - 1)));
                dp[j][0][1] += dp[j - 1][0][0] * (1 + (a[j] & (t - 1)));
            } else {
                dp[j][0][0] += dp[j - 1][0][0] * (1 + (a[j] & (t - 1)));
                dp[j][0][1] += dp[j - 1][0][1] * (1 + (a[j] & (t - 1)));
                dp[j][1][0] += dp[j - 1][1][0] * (1 + (a[j] & (t - 1)));
                dp[j][1][1] += dp[j - 1][1][1] * (1 + (a[j] & (t - 1)));
            }
        }
        if (tmp) 
            break;
        ans += dp[n][1][0];
    }
    cout << ans + (x == 0) - 1 << "\n";
    return 0;
}

T10

洛谷 P3449 PAL-Palindromes

在纸上画画图,可以猜到一个性质:两个回文串能拼起来当且仅当两个的最短循环节相等。因此只需要对每个回文串求出最短循环节,然后把每种循环节的出现次数求平方和即可。

代码
#include <iostream>
#include <map>
#define int long long
using namespace std;
const int P1 = 1000000007;
const int P2 = 1610612741;
const int B = 233;
string str;
int nxt[2000005];
int p() {
    nxt[0] = -1, nxt[1] = 0;
    int n = str.size();
    str = ' ' + str;
    for (int i = 2, j = 0; i <= n; i++) {
        while (j != -1 && str[j + 1] != str[i]) j = nxt[j];
        nxt[i] = j + 1;
        ++j;
    }
    if (n % (n - nxt[n])) 
        return n;
    else 
        return n - nxt[n];
}
pair<int, int> work(int m) {
    int ret1 = 0;
    int ret2 = 0;
    for (int i = 1; i <= m; i++) ret1 = (ret1 * B + str[i] - 'a') % P1;
    for (int i = 1; i <= m; i++) ret2 = (ret2 * B + str[i] - 'a') % P2;
    return make_pair(ret1, ret2);
}
map<pair<int, int>, int> mp;
signed main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int s;
        cin >> s;
        cin >> str;
        mp[work(p())]++;
    }
    int ans = 0;
    for (auto v : mp) ans += v.second * v.second;
    cout << ans << "\n";
    return 0;
}

T11

洛谷 P3443 LIS-The Postman

所有限制实际上可以看做某条边的后继必须是另一条边。可以观察到一条边不能有两个后继,而且限制不能成环。然后跑欧拉回路即可。如果当前点是从某条有后继的边进来的,则必须走这条边的后继。否则往下走时不能走存在前驱的边。

代码
#include <iostream>
#include <map>
using namespace std;
int n, m;
int head[50005], nxt[200005], to[200005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int asdf[200005];
map<int, int> g[50005];
int deg[50005];
int scnt;
int stk[200005];
bool pre[200005];
bool vis[200005];
void dfs(int x, int lim) {
    if (lim) {
        if (vis[lim]) {
            scnt = m + 2;
            return;
        }
        vis[lim] = 1;
        dfs(to[lim], asdf[lim]);
    } else {
        for (int& i = head[x]; i; i = nxt[i]) {
            int v = to[i];
            if (pre[i]) 
                continue;
            pre[i] = 1;
            dfs(v, asdf[i]);
        }
    }
    stk[++scnt] = x;
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int a, b;
        cin >> a >> b;
        add(a, b);
        g[a][b] = ecnt;
        deg[b]++, deg[a]--;
    }
    for (int i = 1; i <= n; i++) {
        if (deg[i]) {
            cout << "NIE\n";
            return 0;
        }
    }
    int lcnt;
    cin >> lcnt;
    while (lcnt--) {
        int k;
        cin >> k;
        k -= 2;
        int x, y, z;
        cin >> x >> y;
        int lst = g[x][y];
        if (!lst) {
            cout << "NIE\n";
            return 0;
        }
        while (k--) {
            cin >> z;
            int tmp = g[y][z];
            if (!tmp || (asdf[lst] && asdf[lst] != tmp)) {
                cout << "NIE\n";
                return 0;
            }
            asdf[lst] = tmp;
            pre[lst = tmp] = 1;
            y = z;
        }
    }
    dfs(1, 0);
    if (scnt != m + 1) 
        cout << "NIE\n";
    else {
        cout << "TAK\n";
        for (int i = scnt; i; i--) cout << stk[i] << "\n";
    }
    return 0;
}

曼哈顿与切比雪夫的互相转化。

贪心贪不起来了可以枚举。

看到绝对值要想到拆。

大胆猜结论。

字符串最短循环节的求法:如果 \((n - nxt[n]) | n\),则为 \(n - nxt[n]\),否则为 \(n\)

可以先枚举,后 dp。

合并限制。

posted @   forgotmyhandle  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
点击右上角即可分享
微信分享提示