AtCoder-ABC-Ex乱写

ABC233Ex Manhattan Christmas Tree

先将 (x,y) 变成 (x+y,xy),也就是曼哈顿转切比雪夫,之后曼哈顿距离 k 的在切比雪夫坐标系下就是一个正方形。

用主席树做矩形和,外层套一个二分即可,时间复杂度 O(nlog2n)

ABC233Ex
#include <bits/stdc++.h>

using namespace std;

struct Point {
    int x, y;
    Point(int _x = 0, int _y = 0): x(_x), y(_y) {}
};
bool operator < (const Point &lhs, const Point &rhs) 
    {return lhs.x != rhs.x ? lhs.x < rhs.x : lhs.y < rhs.y; }

const int N = 1e5 + 10, INF = 0x3f3f3f3f;
int n; Point P[N];
vector<int> lsh[2];

namespace SegTree {
    const int M = 3e6 + 10;
    int root[N], tot = 0;
    int lc[M], rc[M], C[M];
    inline int New() {++tot; lc[tot] = rc[tot] = C[tot] = 0; return tot; }
    inline void maintain(int u) {C[u] = C[lc[u]] + C[rc[u]]; }
#define mid ((l + r) >> 1)
    void Add(int &shadow, int &u, int l, int r, int p, int x) {
        u = New(); lc[u] = lc[shadow], rc[u] = rc[shadow], C[u] = C[shadow];
        if(l == r) {C[u] += x; return; }
        if(p <= mid) Add(lc[shadow], lc[u], l, mid, p, x);
        else Add(rc[shadow], rc[u], mid + 1, r, p, x);
        maintain(u);
    }
    int Ask(int u, int l, int r, int L, int R) {
        if(!u) return 0;
        if(l >= L && r <= R) return C[u];
        if(mid >= R) return Ask(lc[u], l, mid, L, R);
        else if(mid < L) return Ask(rc[u], mid + 1, r, L, R);
        else return Ask(lc[u], l, mid, L, R) + Ask(rc[u], mid + 1, r, L, R);
    }
#undef mid
}

inline bool check(int x, int y, int mid, int k) {
    int siz = 0;
    int l, r; l = upper_bound(lsh[1].begin(), lsh[1].end(), y - mid - 1) - lsh[1].begin() + 1;
    r = upper_bound(lsh[1].begin(), lsh[1].end(), y + mid) - lsh[1].begin();
    int p = upper_bound(P + 1, P + n + 1, Point(x + mid, INF)) - P - 1;
    siz += SegTree::Ask(SegTree::root[p], 1, n, l, r);
    p = upper_bound(P + 1, P + n + 1, Point(x - mid - 1, INF)) - P - 1;
    siz -= SegTree::Ask(SegTree::root[p], 1, n, l, r);
    return siz >= k;
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> n;
    for(int i = 1; i <= n; i ++) {
        int x, y; cin >> x >> y;
        P[i].x = x + y, P[i].y = x - y;
        lsh[0].emplace_back(x + y), lsh[1].emplace_back(x - y);
    }
    sort(lsh[0].begin(), lsh[0].end()), sort(lsh[1].begin(), lsh[1].end());
    lsh[0].erase(unique(lsh[0].begin(), lsh[0].end()), lsh[0].end());
    lsh[1].erase(unique(lsh[1].begin(), lsh[1].end()), lsh[1].end());

    sort(P + 1, P + n + 1);
    for(int i = 1; i <= n; i ++) 
        P[i].y = lower_bound(lsh[1].begin(), lsh[1].end(), P[i].y) - lsh[1].begin() + 1;

    for(int i = 1; i <= n; i ++) 
        SegTree::Add(SegTree::root[i - 1], SegTree::root[i], 1, n, P[i].y, 1);

    int T; cin >> T;
    while(T --) {
        int x, y, t_x, t_y, k; cin >> t_x >> t_y >> k;
        x = t_x + t_y, y = t_x - t_y;
        int L = 0, R = 200000;
        while(L < R) {
            int mid = (L + R) >> 1;
            if(check(x, y, mid, k)) R = mid;
            else L = mid + 1;
        }
        cout << L << '\n';
    }

    return 0;
}

ABC234Ex Enumerate Pairs

浪费我感情的精巧暴力题。

把整个第一象限划分成一堆 k×k 的正方形,显然符合条件的点对要么在同一个正方形中,要么在两个有公共点的正方形中。

于是我们就可以对每个点,只检查以它所在正方形为中心的 3×3 个正方形内部的点,接下来我们证明复杂度是有保证的。

将一个正方形再等分为 4k2×k2 的小正方形,每个区域内部的点对必然满足条件。设这个正方形内部共有 a+b+c+d=w 个点,其中 a,b,c,d 为四个小区域内部的点数,不妨设 abcd,那么这个正方形内部的,符合条件的点对数量就有 (a2)+(b2)+(c2)+(d2)

注意到 4aa+b+c+d=w,因此 (a2)=a(a1)2w(w4)32,因此每个正方形内部的点数是 O(A),其中 A 是答案数量,于是暴力枚举的时间复杂度本质是是 O(nA),其中 A 是答案数量。

ABC234Ex
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

struct Point {
    ll x, y;
    Point(ll _x = 0, ll _y = 0): x(_x), y(_y) {}
};

const int N = 2e5 + 10;
const ll B = 1e9 + 1;
int n; long long k; Point P[N];
map<ll, vector<int>> Map;
vector<pair<int, int>> ans;

inline bool check(Point a, Point b) {
    long long d = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
    return d <= k * k;
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> n >> k;
    for(int i = 1; i <= n; i ++) {
        cin >> P[i].x >> P[i].y;
        ll buc = (P[i].x / k) * B + (P[i].y / k);
        Map[buc].emplace_back(i);
    }
    for(int i = 1; i <= n; i ++) {
        ll buc = (P[i].x / k) * B + (P[i].y / k);
        static ll delta[] = {-B, -B - 1, -1, B - 1, B, B + 1, 1, -B + 1, 0};
        for(auto d : delta) {
            if(!Map.count(buc + d)) continue;
            vector<int> &vec = Map[buc + d];
            for(auto x : vec) 
                if(x < i && check(P[i], P[x])) ans.push_back({x, i});
        }
    }
    sort(ans.begin(), ans.end());
    cout << ans.size() << '\n';
    for(auto pr : ans)
        cout << pr.first << ' ' << pr.second << '\n';

    return 0;
}

ABC235Ex Painting Weighted Graph

把Kruskal重构树建出来,但是要稍微修改一下:将原来的二叉树改成多叉树,也就是不存在两个权值相同的点为父子关系,在连通性上的表现就是:将边权相同的边一起加入到图中。

例如若有三个点 1,2,3,两两之间有权值为 1 的边,我们建出的Kruskal重构树并不形如 1,2 的父亲是 43,4 的父亲是 5,而是 1,2,3 共有一个父亲 4

对于一个最终可以到达的状态,不妨就在最小的操作次数内到达它:一次操作可以看作是在我们的Kruskal重构树上选择一个点并将它子树内的所有叶子染色,因此若两个染色关系为祖先后代关系,那么深度较大的染色无效。

然后就可以在树上做dp了,设 dpu,i 表示在 u 的子树内操作了 i 次,并且所有操作都有效,得到的最终状态数量,容易用背包转移。

根据树上背包的结论,这部分的时间复杂度为 O(nk),总时间复杂度 O(nk+(n+m)logm)

ABC235Ex
#include <bits/stdc++.h>

using namespace std;

struct edge {
    int u, v, w;
    edge() {}
    edge(int _u, int _v, int _w): u(_u), v(_v), w(_w) {}
};
bool operator < (const edge &lhs, const edge &rhs) {return lhs.w < rhs.w; }

const int N = 1e5 + 10, K = 510, MOD = 998244353;
inline int Plus(int a, int b) {return a + b >= MOD ? a + b - MOD : a + b; }
inline int Minus(int a, int b) {return a - b < 0 ? a - b + MOD : a - b; }
inline int ksm(long long a, int b) {
    long long r = 1;
    for(; b; b >>= 1, a = a * a % MOD)
        if(b & 1) r = r * a % MOD;
    return r;
}
int n, m, k; vector<edge> edges;

int p[N], id[N], tot;
int find(int x) {return x == p[x] ? x : p[x] = find(p[x]); }

vector<int> G[2 * N];
inline void add(int a, int b) {G[a].emplace_back(b); }

inline void calc(set<int> &merged) {
    for(auto u : merged)
        if(u == find(u)) add(tot + 1, id[u]), id[u] = ++tot;
    for(auto u : merged)
        if(u != find(u)) add(id[find(u)], id[u]);
    merged.clear();
}

int dp[2 * N][K], siz[2 * N];
inline void solve(int u, int v) {
    static int f[K];
    for(int i = 0; i <= min(siz[u] + siz[v], k); i ++)
        f[i] = 0;
    for(int i = 0; i <= min(siz[u], k); i ++)
        for(int j = 0; j <= min(siz[v], k - i); j ++)
            f[i + j] = Plus(f[i + j], 1ll * dp[u][i] * dp[v][j] % MOD);
    siz[u] += siz[v];
    for(int i = 0; i <= min(siz[u], k); i ++)
        dp[u][i] = f[i];
}
void dfs(int u) {
    dp[u][0] = 1, siz[u] = 1;
    for(auto v : G[u]) 
        dfs(v), solve(u, v);
    if(G[u].size() != 0 && G[u].size() <= k) {
        dp[u][G[u].size()] = Minus(dp[u][G[u].size()], 1);
    }
    dp[u][1] = Plus(dp[u][1], 1);
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> n >> m >> k; edges.resize(m);
    for(int i = 0; i < m; i ++)
        cin >> edges[i].u >> edges[i].v >> edges[i].w;
    sort(edges.begin(), edges.end());

    tot = n;
    for(int i = 1; i <= n; i ++)
        p[i] = id[i] = i;
    set<int> merged;
    for(int i = 0; i < m; i ++) {
        if(i > 0 && edges[i].w != edges[i - 1].w) 
            calc(merged);
        int a = edges[i].u, b = edges[i].v;
        if(find(a) == find(b)) continue;
        merged.insert(find(a)), merged.insert(find(b));
        p[find(a)] = find(b);
    }
    calc(merged);

    dp[0][0] = 1; int root = 0;
    for(int i = 1; i <= n; i ++) 
        if(find(i) == i) 
            dfs(id[i]), solve(root, id[i]);
    int ans = 0;
    for(int i = 0; i <= k; i ++)
        ans = Plus(ans, dp[root][i]);
    cout << ans << '\n';

    return 0;
}

ABC236Ex Distinct Multiples

Ai=Aj 则在 i,j 两点之间连边,那么一个合法方案就是没有边的方案。

所有边将整张图划分为了一堆连通块,设边集 U={(i,j):1i<jn}fi 表示恰有 i 条边的方案数,gi 表示钦定有 i 条边的方案数,那么

gi=j=in(n1)2(ji)fj

二项式反演立即得到

fi=j=in(n1)2(1)ji(ji)gj

答案即 f0,有

f0=i=0n(n1)2(1)igi

只需要算出 g 就好了,考虑枚举最终构成的连通块形状 PP 是一个集族,每个元素是一个点集,并且所有元素的并集为所有点)。对于点集 S,记 lcm(S) 表示这些 iSAilcm,那么答案可以写作

P(SPMlcm(S))EU[E构成的连通块恰为P](1)|E|

对于后面那个 EU[E构成的连通块恰为P](1)|E|,只和 P 中每个点集的点数有关,设 k 个点之间连边构成一个连通块的 (1)边数个数 之和为 hk,那么它就等于

SPh|S|

h 是可以算的:没有连通限制时,(1)边数个数 之和为 [k=1],也就是 [xk]exph=[k=1],取 ln 得到 h=lnx,也就是 hk=(1)k1(k1)!

于是答案就是

P(SPMlcm(S))(SP(1)|S|1(|S|1)!)

子集卷积 exp 优化可以做到 O((n2+logm)2n),暴力枚举子集做转移是 O(3n+2nlogm) 的。

O(3n+2nlogm)
#include <bits/stdc++.h>

using namespace std;

const int N = 16, MOD = 998244353;
inline int Plus(int a, int b) {return a + b >= MOD ? a + b - MOD : a + b; }
inline int Minus(int a, int b) {return a - b < 0 ? a - b + MOD : a - b; }
int n; long long m, D[N], w[N + 10];

inline long long lcm(long long a, long long b) {
    if(a > m || b > m) return m + 1;
    __int128 ans = (__int128)a / __gcd(a, b) * b;
    return ans > m ? m + 1 : ans;
}
inline int pcnt(int x) {return __builtin_popcount(x); }
long long L[1 << N], f[1 << N], g[1 << N];

int main() {
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> n >> m;
    for(int i = 0; i < n; i ++)
        cin >> D[i];
    L[0] = 1;
    for(int i = 1; i < (1 << n); i ++)
        L[i] = lcm(L[i ^ (i & -i)], D[__lg(i & -i)]);
    for(int i = 0; i < (1 << n); i ++)
        g[i] = (m / L[i]) % MOD;

    w[1] = 1;
    for(int i = 2; i <= n; i ++)
        w[i] = 1ll * w[i - 1] * (i - 1) % MOD;
    for(int i = 0; i <= n + 1; i += 2)
        w[i] = Minus(0, w[i]);
    f[0] = 1;
    for(int i = 1; i < (1 << n); i ++) {
        int u = __lg(i & -i);
        for(int j = i; j; j = (j - 1) & i) {
            if(!(j >> u & 1)) continue;
            f[i] = Plus(f[i], 1ll * f[i ^ j] * g[j] % MOD * w[pcnt(j)] % MOD);
        }
    }
    cout << f[(1 << n) - 1] << '\n';

    return 0;
}
O((n2+logm)2n)
#include <bits/stdc++.h>

using namespace std;

const int N = 16, MOD = 998244353;
inline int Plus(int a, int b) {return a + b >= MOD ? a + b - MOD : a + b; }
inline int Minus(int a, int b) {return a - b < 0 ? a - b + MOD : a - b; }
int n; long long m, D[N], w[N + 10];
int inv[N + 1], fac[N + 1], ifac[N + 1];

inline long long lcm(long long a, long long b) {
    if(a > m || b > m) return m + 1;
    __int128 ans = (__int128)a / __gcd(a, b) * b;
    return ans > m ? m + 1 : ans;
}
inline int pcnt(int x) {return __builtin_popcount(x); }
long long L[1 << N];
int f[N + 1][1 << N], g[N + 1][1 << N];

inline void FWT(int A[], int n, int type) {
    for(int h = 2; h <= n; h <<= 1)
        for(int i = 0; i < n; i += h)
            for(int j = i; j < i + (h >> 1); j ++) {
                if(type == 1) A[j + (h >> 1)] = Plus(A[j + (h >> 1)], A[j]);
                else A[j + (h >> 1)] = Minus(A[j + (h >> 1)], A[j]);
            }
}
inline void exp(int f[], int g[], int n) {
    for(int i = 0; i <= n; i ++) g[i] = 0;
    g[0] = 1;
    for(int i = 1; i <= n; i ++) {
        for(int j = 1; j <= i; j ++)
            g[i] = Plus(g[i], 1ll * j * f[j] % MOD * g[i - j] % MOD);
        g[i] = 1ll * g[i] * inv[i] % MOD;
    }
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> n >> m;
    inv[1] = fac[0] = fac[1] = ifac[0] = ifac[1] = 1;
    for(int i = 2; i <= n; i ++) {
        inv[i] = Minus(0, 1ll * (MOD / i) * inv[MOD % i] % MOD);
        fac[i] = 1ll * fac[i - 1] * i % MOD;
        ifac[i] = 1ll * ifac[i - 1] * inv[i] % MOD;
    }
    
    for(int i = 0; i < n; i ++)
        cin >> D[i];
    L[0] = 1;
    for(int i = 1; i < (1 << n); i ++)
        L[i] = lcm(L[i ^ (i & -i)], D[__lg(i & -i)]);
    for(int i = 0; i < (1 << n); i ++)
        f[__builtin_popcount(i)][i] = (m / L[i]) % MOD;

    w[1] = 1;
    for(int i = 2; i <= n; i ++)
        w[i] = 1ll * w[i - 1] * (i - 1) % MOD;
    for(int i = 0; i <= n + 1; i += 2)
        w[i] = Minus(0, w[i]);
    for(int i = 0; i < (1 << n); i ++)
        f[__builtin_popcount(i)][i] = 1ll * f[__builtin_popcount(i)][i] * w[pcnt(i)] % MOD;
    for(int i = 0; i <= n; i ++) 
        FWT(f[i], 1 << n, 1);
    
    for(int mask = 0; mask < (1 << n); mask ++) {
        static int F[N + 1], G[N + 1];
        for(int i = 0; i <= n; i ++)
            F[i] = f[i][mask];
        exp(F, G, n);
        for(int i = 0; i <= n; i ++)
            g[i][mask] = G[i];
    }
    FWT(g[n], 1 << n, -1);
    cout << g[n][(1 << n) - 1] << '\n';

    return 0;
}

ABC237Ex Hakata

根据经典结论:本质不同的回文子串个数只有 O(|S|) 个,我们可以将这些串 O(|S|3) 全部找出来,若串 x 是串 y 的子串,那么就连有向边 (x,y),答案即为这个图的最长反链。

根据Dilworth定理,所求即为最小链覆盖,建二分图,若有原边 (x,y),那么连 x 的左部点到 y 的右部点,跑二分图最大匹配,答案就是左部点个数减去最大匹配。

时间复杂度 O(|S|3)

ABC237Ex
#include <bits/stdc++.h>

using namespace std;


string S;
set<string> Set;
inline bool check(string s) {
    for(int i = 0; i < s.size(); i ++)
        if(s[i] != s[(int)s.size() - 1 - i]) return false;
    return true;
}
inline bool cover(string &x, string &y) {
    if(x.size() > y.size()) return false;
    for(int i = 0; i + x.size() <= y.size(); i ++) {
        bool flag = true;
        for(int j = 0; j < x.size(); j ++)
            if(x[j] != y[i + j]) {flag = false; break; }
        if(flag) return true;
    }
    return false;
}
vector<string> vec;

struct Dinic {
    static const int N = 410, INF = 0x3f3f3f3f;
    struct edge {
        int from, to, cap, flow;
        edge(int u, int v, int c, int f): from(u), to(v), cap(c), flow(f) {}
    };
    vector<edge> edges; vector<int> G[N];
    inline void AddEdge(int u, int v, int c) {
        static int M;
        edges.emplace_back(edge(u, v, c, 0)), edges.emplace_back(edge(v, u, 0, 0));
        M = edges.size(); G[u].emplace_back(M - 2), G[v].emplace_back(M - 1);
    }
    int s, t, dist[N], cur[N];
    inline bool BFS() {
        memset(dist, 0, sizeof dist);
        dist[s] = 1; queue<int> Q; Q.push(s);
        while(!Q.empty()) {
            int u = Q.front(); Q.pop();
            for(auto i : G[u]) {
                edge &e = edges[i];
                if(dist[e.to] || e.flow == e.cap) continue;
                dist[e.to] = dist[u] + 1; Q.push(e.to);
            }
        }
        return dist[t];
    }
    int dfs(int u, int F) {
        if(u == t || F <= 0) return F;
        int flow = 0;
        for(int &i = cur[u]; i < G[u].size(); i ++) {
            auto &e = edges[G[u][i]];
            if(dist[e.to] == dist[e.from] + 1 && e.flow < e.cap) {
                int f = dfs(e.to, min(F, e.cap - e.flow));
                flow += f, F -= f, e.flow += f, edges[G[u][i] ^ 1].flow -= f;
                if(!F) break;
            }
        }
        return flow;
    }
    int MaxFlow(int s, int t) {
        this->s = s, this->t = t;
        int res = 0;
        while(BFS()) {
            memset(cur, 0, sizeof cur);
            res += dfs(s, INF);
        }
        return res;
    }
} solver;

int main() {
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> S;
    for(int i = 0; i < S.size(); i ++) {
        for(int len = 1; len <= i + 1; len ++)
            if(check(S.substr(i - len + 1, len))) 
                Set.insert(S.substr(i - len + 1, len));
    }
    vec.resize(Set.size()); int i = 0;
    for(auto it = Set.begin(); it != Set.end(); it = Set.erase(it)) 
        vec[i ++] = *it;

    int n = vec.size(); int s = 2 * n, t = s + 1;
    for(int i = 0; i < n; i ++)
        solver.AddEdge(s, i, 1), solver.AddEdge(i + n, t, 1);
    for(int i = 0; i < n; i ++) {
        for(int j = 0; j < n; j ++) {
            if(j == i || !cover(vec[i], vec[j])) continue;
            solver.AddEdge(i, j + n, 1);
        }
    }
    cout << n - solver.MaxFlow(s, t) << '\n';

    return 0;
}

[ABC242Ex] Random Painting

只需要求出所有方案的答案和再除以总方案数也就是 n! 就可以知道期望了。

考虑从最终状态回到初始状态的过程:一开始只有最后留下的一个人,每次可以新加进来一个人。

然后考虑区间dp,设 fl,r 表示只考虑第 l 个人到第 r 个人,其中第 l 个人和第 r 个人已经被加进来的答案,设 gl,r 表示相应的方案数。

转移形如

gl,r(rl2kl1)gl,k×gk,r×c1fl,r(rl2kl1)(gl,k×gk,r×c2+fl,k×gk,r×c1+gl,k×fk,r×c1)

其中

c1=[Sl=R]+[Sr=L]c2=[Sl=R](kl)+[Sr=L](rk)

上面组合数的意义是随便排列两部分之间的顺序,c1 表示 k 的选择方案(将 k 删去的人的方案数),c2 表示 k 的选择方案的距离和。kfl,r 的贡献就是方案数乘上距离和,即 gl,k×gk,r×c2,然后左右两部分的贡献再分别算即可,总时间复杂度 O(n3),注意我们是在环上做的区间dp即可。

ABC242Ex
#include <bits/stdc++.h>

using namespace std;

const int N = 310, MOD = 998244353;
inline int Plus(int a, int b) {return a + b >= MOD ? a + b - MOD : a + b; }
inline int Minus(int a, int b) {return a - b < 0 ? a - b + MOD : a - b; }
inline int ksm(long long a, int b) {
    long long r = 1;
    for(; b; b >>= 1, a = a * a % MOD)
        if(b & 1) r = r * a % MOD;
    return r;
}
int fac[N], ifac[N];
inline int C(int a, int b) {return a >= b ? 1ll * fac[a] * ifac[b] % MOD * ifac[a - b]  % MOD : 0; }
int n; char S[N];
int f[N][N], g[N][N];

int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    
    cin >> n >> (S + 1);
    fac[0] = 1; for(int i = 1; i <= n; i ++) fac[i] = 1ll * fac[i - 1] * i % MOD;
    ifac[n] = ksm(fac[n], MOD - 2); for(int i = n; i >= 1; i --) ifac[i - 1] = 1ll * ifac[i] * i % MOD;
    
    for(int i = 1; i < n; i ++) 
        f[i][i + 1] = 0, g[i][i + 1] = 1;
    f[n][1] = 0, g[n][1] = 1;
    
    for(int len = 3; len <= n + 1; len ++) for(int l = 1; l <= n; l ++) {
        int r = l + len - 1; 
        for(int k = l + 1; k < r; k ++) {
            int L = (l > n ? l - n : l), R = (r > n ? r - n : r), K = (k > n ? k - n : k);
            int c1 = (S[L] == 'R') + (S[R] == 'L');
            int c2 = (S[L] == 'R' ? k - l : 0) + (S[R] == 'L' ? r - k : 0);
            g[L][R] = Plus(g[L][R], 1ll * g[L][K] * g[K][R] % MOD * c1 % MOD * C(r - l - 2, k - l - 1) % MOD); 
            int add = 1ll * g[L][K] * g[K][R] % MOD * c2 % MOD;
            add = Plus(add, 1ll * g[L][K] * c1 % MOD * f[K][R] % MOD);
            add = Plus(add, 1ll * g[K][R] * c1 % MOD * f[L][K] % MOD);
            f[L][R] = Plus(f[L][R], 1ll * add * C(r - l - 2, k - l - 1) % MOD);
        }
    }
    
    int ans = 0;
    for(int i = 1; i <= n; i ++)
        ans = Plus(ans, f[i][i]);
    ans = 1ll * ans * ifac[n] % MOD;
    cout << ans << '\n';
    
    return 0;
}

[ABC239Ex] Dice Product 2

f(x) 表示目前的值 x 的期望次数,初始值 f(1)=0,所求即为 f(m+1)

枚举最后一次扔到的点数,有转移

f(x)=1+i=1n1nf(xi)

当右边的求和中 i=1 时我们会用到 f(x),所以将它移到左边然后变形得到

n1nf(x)=1+i=2nf(xi)n

也就是

f(x)=n+i=2nf(xi)n1

此时就只需要知道 f(y),y<x 就可以得到 f(x) 了,可以递推求得所有 f(x)

复杂度显然是不对的,假如要求出所有 f(1..m+1),复杂度是 O(nn) 的(默认 n,m 同阶)。但是我们只需要知道 f(m+1) 即可,可以记忆化搜索,只计算需要用到的 f 值,下面我们证明时间复杂度是 O(n3/4),用哈希表记忆化,可以通过。

时间复杂度部分:

假如我们要计算 f(m),设 L=m,将所有待计算的 f(x) 分为两份:

  • x[1,L],这部分的时间复杂度是 L×O(L)=O(m3/4)
  • x=m/1,m/2,m/3,,m/L,用积分估计这部分的和,是 O(1Lm/x)=O(m3/4)的。

因此总时间复杂度为 O(m3/4)

ABC239Ex
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>

using namespace std;
using namespace __gnu_pbds;

const int MOD = 1e9 + 7;
inline int Plus(int a, int b) {return a + b >= MOD ? a + b - MOD : a + b; }
inline int Minus(int a, int b) {return a - b < 0 ? a - b + MOD : a - b; }
inline int ksm(long long a, int b) {
    long long r = 1;
    for(; b; b >>= 1, a = a * a % MOD)
        if(b & 1) r = r * a % MOD;
    return r;
}
int n, T;

gp_hash_table<int, int> F;
inline int f(int x) {
    if(F.find(x) != F.end()) return F[x];
    int ans = n, t = x - 1;
    for(int l = 2, r; l <= n; l = r + 1) {
        r = min(n, (t < l ? n : t / (t / l)));
        ans = Plus(ans, 1ll * f(t / l + 1) * (r - l + 1) % MOD);
    }
    return F[x] = 1ll * ans * T % MOD;
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0);

    int m; cin >> n >> m;
    T = ksm(n - 1, MOD - 2);
    F[1] = 0, cout << f(m + 1) << '\n';

    return 0;
}

[ABC240Ex] Sequence of Substrings

一个暴力的想法是将所有子串插入到Trie中,然后设 fi 表示前 i 个字符的答案,在Trie上按照字典序走,每次走的时候做一次转移(枚举所有该点的串,然后用 fposlen+1 转移到 fpos)。

假如用树状数组做单点更新最大值、查询前缀最大值,可以做到 O(n2logn)

注意到最终答案里相邻两个串要么长度不变或变小,要么长度加一(若长度加了二,可以删去最后一个字符,方案依然合法),于是只有长度不超过 2n 的子串是有用的,于是只需要将这些串插入到Trie中做,时间复杂度就变为 O(nnlogn) 了,可以通过。

ABC240Ex
#include <bits/stdc++.h>

using namespace std;

const int N = 2.5e4 + 10, M = 250;
int n, m; char S[N];

struct Fenwick {
    int C[N];
    Fenwick() {memset(C, 0, sizeof C); }
    inline void upd(int p, int x) {for(; p <= n; p += p & -p) C[p] = max(C[p], x); }
    inline int ask(int p) {int r = 0; for(; p; p -= p & -p) r = max(r, C[p]); return r; }
} BIT;

namespace Trie {
    int ch[N * M][2], tot = 0;
    vector<int> pos[N * M];
    inline void insert(int p) {
        int u = 0;
        for(int i = p; i <= min(n, p + m); i ++) {
            if(!ch[u][S[i] - '0']) ch[u][S[i] - '0'] = ++tot;
            u = ch[u][S[i] - '0'];
            pos[u].emplace_back(i);
        }
    }
    vector<pair<int, int>> vec;
    void dfs(int u, int dep) {
        vec.clear();
        for(auto x : pos[u]) 
            vec.push_back({x, BIT.ask(x - dep) + 1});
        for(auto pr : vec)
            BIT.upd(pr.first, pr.second);
        if(ch[u][0]) dfs(ch[u][0], dep + 1);
        if(ch[u][1]) dfs(ch[u][1], dep + 1);
    }
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> n >> (S + 1);
    m = (int)sqrt(2 * n);
    for(int i = 1; i <= n; i ++)
        Trie::insert(i);
    Trie::dfs(0, 0);
    cout << BIT.ask(n) << '\n';

    return 0;
}

[ABC241Ex] Card Deck Score

答案即为

[xm]i=1n1(aix)bi+11aix

但是 m1018 级别的,直接求逆是不现实的,因此我们尝试将需要求逆的部分做一下变换,具体地,原式等于

[xm](i=1n(1(aix)bi+1))(i=1n11aix)

只关注 i=1n11aix 的部分,用部分分式定理,它一定可以写成

i=1n11aix=i=1nTi1aix

两边同乘 i=1n(1aix),得到

1=i=1nTiji(1ajx)

分别代入 x=1ai,得到

1=Tiji(1ajai)

移项就立即得到

Ti=1ji(1ajai)

右边是可以 O(n) 求得的,于是我们可以 O(n2) 求得所有 Ti

知道了所有 Ti,我们就可以知道

[xc]i=1n11aix=i=1n[xc]Ti1aix=i=1nTiaic

后面就是白给内容了,O(n2n) 暴力求出来 i=1n(1(aix)bi+1),对其中的每一项 cxk,求出 t=[xmk]i=1n11aix,然后将 c×t 累加到答案中即可,总时间复杂度 O(n2n+n2)

ABC241Ex
#include <bits/stdc++.h>

using namespace std;

const int N = 20, MOD = 998244353;
inline int Plus(int a, int b) {return a + b >= MOD ? a + b - MOD : a + b; }
inline int Minus(int a, int b) {return a - b < 0 ? a - b + MOD : a - b; }
inline int ksm(long long a, long long b) {
    long long r = 1;
    for(; b; b >>= 1, a = a * a % MOD)
        if(b & 1) r = r * a % MOD;
    return r;
}

int n, A[N], iA[N], T[N]; long long m, B[N];
inline void merge(map<long long, int> &P, map<long long, int> &Q) {
    map<long long, int> ans;
    for(auto x : P) for(auto y : Q) {
        if(x.first + y.first > m) continue;
        ans[x.first + y.first] = Plus(ans[x.first + y.first], 1ll * x.second * y.second % MOD);
    }
    P.swap(ans);
}
map<long long, int> f;

int main() {
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> n >> m;
    for(int i = 1; i <= n; i ++)
        cin >> A[i] >> B[i], iA[i] = ksm(A[i], MOD - 2);
    
    for(int i = 1; i <= n; i ++) {
        T[i] = 1;
        for(int j = 1; j <= n; j ++) {
            if(j == i) continue;
            T[i] = 1ll * T[i] * Minus(1, 1ll * A[j] * iA[i] % MOD) % MOD;
        }
        T[i] = ksm(T[i], MOD - 2);
    }

    f[0] = 1, f[B[1] + 1] = Minus(0, ksm(A[1], B[1] + 1));
    for(int i = 2; i <= n; i ++) {
        map<long long, int> tmp;
        tmp[0] = 1, tmp[B[i] + 1] = Minus(0, ksm(A[i], B[i] + 1));
        merge(f, tmp);
    }

    int ans = 0;
    for(auto pr : f) {
        long long r = m - pr.first, mul = 0;
        if(r < 0) continue;
        for(int i = 1; i <= n; i ++)
            mul = Plus(mul, 1ll * T[i] * ksm(A[i], r) % MOD);
        ans = Plus(ans, 1ll * pr.second * mul % MOD);
    }
    cout << ans << '\n';

    return 0;
}

[ABC242Ex] Random Painting

Ai 表示第 i 个方块被染色的时间,所求即为 E(maxAi)

根据min-max容斥,设全集 U=1,2,,n,所求即为

E(maxxUAx)=SU(1)|S|+1E(minxSAx)

cS 表示有多少个小球对应的区间覆盖到了集合 S 中的某个元素,那么每次染色染到 S 中任一元素的概率即为 cSm,于是 E(minxSAx)=mcS

所求即为

SU(1)|S|+1mcS

dp,设 fi,j,0/1 表示只考虑前 i 个小方块,钦定 iScS=j 并且 (|S|+1)mod2=0/1 的方案数,枚举倒数第二个在 S 中的小方块 k,有转移

fi,j,0/1=k=0i1fk,jgk+1,1/0

其中 gk+1 表示 L[k+1,i],R[i,n] 的小球数量,容易求得,总时间复杂度 O(n2m)

ABC242Ex
#include <bits/stdc++.h>

using namespace std;

const int N = 410, MOD = 998244353;
inline int Plus(int a, int b) {return a + b >= MOD ? a + b - MOD : a + b; }
inline int Minus(int a, int b) {return a - b < 0 ? a - b + MOD : a - b; }
inline int ksm(long long a, int b) {
    long long r = 1;
    for(; b; b >>= 1, a = a * a % MOD)
        if(b & 1) r = r * a % MOD;
    return r;
}

int n, m, L[N], R[N], siz_L[N];
vector<int> all_R[N];
int f[N][N][2], g[N];

int main() {
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> n >> m;
    for(int i = 1; i <= m; i ++) {
        cin >> L[i] >> R[i];
        siz_L[L[i]] ++, all_R[R[i]].emplace_back(L[i]);
    }

    f[0][0][1] = 1;
    for(int i = 1; i <= n; i ++) {
        for(int j = 1; j <= i; j ++)
            g[j] += siz_L[i];
        for(auto l : all_R[i - 1])
            for(int j = 1; j <= l; j ++)
                g[j] --;
        for(int j = 0; j <= m; j ++) {
            for(int k = 0; k < i; k ++) 
                if(j - g[k + 1] >= 0) {
                    f[i][j][0] = Plus(f[i][j][0], f[k][j - g[k + 1]][1]),
                    f[i][j][1] = Plus(f[i][j][1], f[k][j - g[k + 1]][0]);
                }
        }
    }

    static int c[N];
    memset(c, 0, sizeof c);
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            c[j] = Plus(c[j], Minus(f[i][j][0], f[i][j][1]));
    int ans = 0;
    for(int i = 1; i <= m; i ++)
        ans = Plus(ans, 1ll * c[i] * ksm(i, MOD - 2) % MOD);
    ans = 1ll * ans * m % MOD;
    cout << ans << '\n';
    
    return 0;
}
posted @   313LA  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示