20250207

T1

5246 链上游戏

首先注意到一个排列的答案仅与其逆序对数量有关。然后如果钦定不做重排操作,则可以算出每个逆序对数时的答案,记为 \(f_i\)。然后考虑如果做重排操作,代价就会变成所有 \(f_i\) 的带权平均数 \(X\) 加上 \(c\)。因此做重排的一定是原本 \(f_i\) 较大的 \(i\)。枚举 \(X + c\) 的范围,将所有超过 \(X + c\) 的 \(f\) 变成 \(X + c\),可以算出一个带权平均数 \(X^\prime\),令 \(X^\prime = X\) 可以解出 \(X\),然后检查 \(X + c\) 是否落在当前范围内即可。然后还需要求长为 \(n\) 的排列逆序对数为 \(k\) 的方案数,这个随便 dp 即可。

代码
#include <iostream>
#include <algorithm>
#include <string.h>
#define int __int128
using namespace std;
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
int f[20][305];
int dp[305];
signed A[305];
struct Fraction {
    int a, b;
    Fraction(int x = 0, int y = 1) { a = x, b = y; }
    Fraction operator()() {
        int g = gcd(a, b);
        a /= g, b /= g;
        return *this;
    }
};
ostream& operator<<(ostream& out, Fraction x) {
    out << (long long)x.a << "/" << (long long)x.b;
    return out;
}
Fraction operator+(Fraction a, Fraction b) {
    int c = a.b * b.b / gcd(a.b, b.b);
    a.a *= a.b / c, b.a *= b.b / c;
    return ((Fraction) { a.a + b.a, c })();
}
Fraction operator+(Fraction a, int b) {
    Fraction c(a.b * b + a.a, a.b);
    return c();
}
Fraction operator*(Fraction a, Fraction b) { return ((Fraction) { a.a * b.a, a.b * b.b })(); }
Fraction operator*(Fraction a, int b) { return ((Fraction) { a.a * b, a.b })(); }
Fraction min(int a, Fraction b) {
    Fraction c(a * b.b, b.b);
    return ((c.a < b.a) ? c : b)();
}
pair<int, int> p[305];
signed main() {
    freopen("line.in", "r", stdin);
    freopen("line.out", "w", stdout);
    f[0][0] = 1;
    for (int i = 0; i < 16; i++) {
        for (int j = 0; j <= i * (i - 1) / 2; j++) {
            for (int k = 0; k <= i; k++) 
                f[i + 1][j + k] += f[i][j];
        }
    }
    signed tc;
    cin >> tc;
    while (tc--) {
        long long n, a, b, c, d, N;
        cin >> n >> a >> b >> c >> d;
        N = n * (n - 1) / 2;
        memset(dp, 0, sizeof dp);
        for (int i = 1; i <= N; i++) {
            if (i <= N - i) 
                dp[i] = dp[i - 1] + a;
            else 
                dp[i] = min(dp[i - 1] + a, dp[N - i] + b);
        }
        for (int i = 0; i <= N; i++) p[i] = make_pair(dp[i], i);
        sort(p, p + N + 1);
        Fraction X(2147483647, 1);
        int fac = 1;
        for (int i = 1; i <= n; i++) fac = fac * i;
        for (int i = 0, sp = 0, sd = 0; i < N; i++) {
            sp += f[n][p[i].second], sd += f[n][p[i].second] * p[i].first;
            if (max((__int128)c, p[i].first) * sp <= sd + c * fac && sd + c * fac <= p[i + 1].first * sp) {
                X = (Fraction) { sd + c * (fac - sp), sp } + c;
                break;
            }
        }
        while (d--) {
            int cnt = 0;
            for (int i = 1; i <= n; i++) {
                cin >> A[i];
                for (int j = 1; j < i; j++) cnt += (A[j] > A[i]);
            }
            cout << min(dp[cnt], X) << "\n";
        }
    }
    return 0;
}

T2

5247 因式分解

设分圆多项式为 \(\Phi_n(x)\),则有 \(x^n- 1 = \prod\limits_{d | n} \Phi_d(x)\)。两边 \(\ln\) 然后由莫反可以知道 \(\Phi_n(x) = \prod\limits_{d | n}(x^d - 1)^{\mu(\frac{n}{d})}\)。于是只需要这样的多项式乘除。乘法就相当于一个 \(0/1\) 背包,而除法考虑用 \(\frac{1}{x - 1} = -\sum\limits_{i = 0}x^i\) 转化为无限背包。由于 \(\deg(\Phi_x) = \varphi(x)\),这个是可以过的。但是也可以通过找规律来递推 \(\Phi_n\)。我写的是第二种。

代码
#include <iostream>
#include <algorithm>
#include <vector>
#define int long long
using namespace std;
vector<int> fac[2000005], poly[2000005];
int mu[2000005], phi[2000005], p[2000005], mx[2000005], pcnt;
bool mark[2000005];
void Sieve(int n) {
    phi[1] = mu[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!mark[i]) {
            mu[i] = -1, phi[i] = i - 1;
            p[++pcnt] = i;
        }
        for (int j = 1; j <= pcnt && 1ll * p[j] * i <= n; j++) {
            mark[p[j] * i] = 1;
            if (i % p[j] == 0) {
                mu[p[j] * i] = 0;
                phi[p[j] * i] = phi[i] * p[j];
                break;
            }
            mu[p[j] * i] = -mu[i];
            phi[p[j] * i] = phi[i] * phi[p[j]];
        }
    }
    for (int i = 1; i <= n; i++) {
        if (!mu[i]) 
            continue;
        for (int j = i; j <= n; j += i) 
            mx[j] = max(mx[j], i);
    }
}
bool cmp(vector<int> &a, vector<int> &b) {
    if (a.size() != b.size()) 
        return a.size() < b.size();
    for (int i = (int)a.size() - 1; ~i; i--) {
        if (a[i] != b[i]) 
            return a[i] < b[i];
    }
    return 0;
}
void print(vector<int> &a) {
    cout << "(";
    int n = a.size();
    if (a[n - 1] < 0) 
        for (int i = 0; i < n; i++) a[i] = -a[i];
    for (int i = n - 1; ~i; i--) {
        if (a[i] > 0) {
            if (i != (int)a.size() - 1) 
                cout << "+";
            if (a[i] != 1 || i == 0) 
                cout << a[i];
            if (i != 0) {
                cout << "x";
                if (i != 1) 
                    cout << "^" << i;
            }
        } else if (a[i] < 0) {
            if (a[i] != -1 || i == 0) 
                cout << a[i];
            else 
                cout << "-";
            if (i != 0) {
                cout << "x";
                if (i != 1) 
                    cout << "^" << i;
            }
        }
    }
    cout << ")";
}
signed main() {
    freopen("factorization.in", "r", stdin);
    freopen("factorization.out", "w", stdout);
    Sieve(2000000);
    int tc;
    cin >> tc;
    while (tc--) {
        vector<vector<int> > ans;
        int n;
        cin >> n;
        for (int i = 1; i <= n; i++) {
            if (n % i == 0) {
                for (int j = i; j <= n; j += i) 
                    n % j == 0 ? (fac[j].emplace_back(i), 0) : 0;
            }
        }
        for (auto x : fac[n]) {
            poly[x].clear(), poly[x].resize(phi[x] + 1, 0), poly[x][0] = 1;
            if (!mu[x]) {
                for (int j = 0; j <= phi[mx[x]]; j++) 
                    poly[x][j * x / mx[x]] = poly[mx[x]][j];
            } else {
                for (auto v : fac[x]) {
                    if (mu[v] == mu[x]) {
                        for (int j = phi[x]; j >= v; j--) 
                            poly[x][j] -= poly[x][j - v];
                    }
                }
                for (auto v : fac[x]) {
                    if (mu[v] != mu[x]) {
                        for (int j = v; j <= phi[x]; j++) 
                            poly[x][j] += poly[x][j - v];
                    }
                }
            }
            ans.emplace_back(poly[x]);
        }
        sort(ans.begin(), ans.end(), cmp);
        for (auto v : ans) print(v);
        for (auto v : fac[n]) v != n ? (fac[v].clear(), poly[v].clear()) : void();
        fac[n].clear(), poly[n].clear();
        cout << "\n";
    }
    return 0;
}

T3

1360 想象

首先我们需要一个树上圆理论。设点集 \(S\) 的直径长度为 \(len(S)\),直径中点为 \(mid(S)\)。定义树上一个“圆”(邻域) \(c(u, r) = \{ x | dist(x, u) \leq r \}\)。定义一个点集 \(S\) 的最小覆盖圆 \(c(S)\) 为 \(c(u, r)\) 满足 \(S \subseteq c(u, r)\) 且 \(r\) 最小。容易发现 \(S\) 的最小覆盖圆 \(c(S)\) 即为 \(c(mid(S), \frac{len(S)}{2})\)。然后是有关最小覆盖圆合并的结论:对于点集 \(S, T\),若 \(c(S) \subseteq c(T)\),则 \(c(S \cup T) = c(T)\);若 \(c(T) \subseteq c(S)\),则 \(c(S \cup T) = c(S)\)。否则两圆相交或相离,有 \(len(S \cup T) = dist(mid(S), mid(T)) + \frac{len(S) + len(T)}{2}\)。

接下来考虑分治,设当前 \([l, r]\),中点 \(mid\),则需要计算所有 \([l, mid] \cup (mid, r]\) 的直径和。考虑固定 \([l, mid]\),随着 \(r\) 的增加,显然 \(c((mid, r])\) 也是不断扩大,因此 \(c([l, mid])\) 与 \(c((mid, r])\) 的关系一定是包含 \(\rightarrow\) 相交 / 相离 \(\rightarrow\) 被包含。这两条分界线随着 \(l\) 的减小显然都会单调向右移。因此只需要两个指针即可维护。然后考虑算贡献。包含与被包含两部分的贡献是容易计算的,而中间一部分有一个形如求 \(\sum\limits_{p \le y \le q} dist(x, y)\) 的子问题。如果利用 这题 的套路使用树剖 + 树状数组维护,则复杂度是 \(\mathcal{O}(n\log^3n)\),而使用点分树则是 \(\mathcal{O}(n\log^2n)\)。但是由于前者常数极小,跑得飞快。\(\log\) 越多跑得越快

代码
#include <iostream>
#define lowbit(x) ((x) & (-(x)))
using namespace std;
int n;
int head[200005], nxt[400005], to[400005], ecnt;
inline void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int dep[200005], _dfn[200005], dfn[200005], fa[200005], ncnt;
int top[200005], sz[200005], son[200005];
void dfs1(int x, int fa) {
    sz[x] = 1;
    ::fa[x] = fa;
    dep[x] = dep[fa] + 1;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) {
            dfs1(v, x);
            sz[x] += sz[v];
            if (sz[v] > sz[son[x]]) 
                son[x] = v;
        }
    }
}
void dfs2(int x, int t) {
    _dfn[dfn[x] = ++ncnt] = x;
    top[x] = t;
    if (son[x]) 
        dfs2(son[x], t);
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != son[x] && v != fa[x]) 
            dfs2(v, v);
    }
}
inline int LCA(int x, int y) {
    while (top[x] ^ top[y]) (dep[top[x]] < dep[top[y]]) ? (y = fa[top[y]]) : (x = fa[top[x]]);
    return (dep[x] < dep[y]) ? x : y;
}
inline int dist(int x, int y) { return dep[x] + dep[y] - dep[LCA(x, y)] * 2; }
inline int Kanc(int x, int K) {
    while (dep[x] - dep[top[x]] < K) K -= (dep[x] - dep[top[x]] + 1), x = fa[top[x]];
    return _dfn[dfn[x] - K];
}
int go(int x, int y, int k) {
    int z = LCA(x, y);
    return (dep[x] - dep[z] >= k) ? Kanc(x, k) : Kanc(y, dep[x] + dep[y] - dep[z] * 2 - k);
}
struct node {
    int a, b, d;
    void add(int x) {
        int da = dist(x, a), db = dist(x, b);
        da > db ? (swap(da, db), swap(a, b)) : void();
        if (db > d) 
            d = db, a = x;
    }
};
struct BIT {
    long long bit[200005];
    inline void add(int x, int y) { for (; x < n * 2; x += lowbit(x)) bit[x] += y; }
    long long query(int x) {
        long long ret = 0;
        for (; x; x -= lowbit(x)) ret += bit[x];
        return ret;
    }
} bit1, bit2;
inline void Add(int l, int r, int v) {
    bit1.add(l, v), bit1.add(r + 1, -v);
    bit2.add(l, -l * v), bit2.add(r + 1, (r + 1) * v);
}
inline long long Query(int l, int r) { return bit1.query(r) * (r + 1) + bit2.query(r) - bit1.query(l - 1) * l - bit2.query(l - 1); }
void Add(int x, int v) {
    while (top[x] ^ 1) Add(dfn[top[x]], dfn[x], v), x = fa[top[x]];
    (x != 1) ? Add(dfn[1] + 1, dfn[x], v) : void();
}
long long Query(int x) {
    long long ret = 0;
    while (top[x] ^ 1) ret += Query(dfn[top[x]], dfn[x]), x = fa[top[x]];
    (x != 1) ? (ret += Query(dfn[1] + 1, dfn[x])) : 0;
    return ret;
}
long long Ans;
int len[200005], md[200005];
void Solve(int l, int r) {
    if (l == r) 
        return;
    if (r == l + 1) {
        Ans += dist(l, r);
        return;
    }
    int mid = (l + r) >> 1;
    long long sr = 0, sl = 0;
    node t = (node) { mid + 1, mid + 1, 0 };
    for (int i = mid + 1; i <= r; i++) {
        t.add(i);
        len[i] = t.d, md[i] = go(t.a, t.b, t.d >> 1);
        sr += len[i];
    }
    t = (node) { mid, mid, 0 };
    int p = mid + 1, q = mid + 1;
    for (int i = mid; i >= l; i--) {
        t.add(i);
        int xlen = t.d, xmid = go(t.a, t.b, t.d >> 1);
        while (q <= r && !(dist(xmid, md[q]) + xlen / 2 <= len[q] / 2)) sr -= len[q], sl += len[q] / 2 + dep[md[q]], Add(md[q++], 1);
        while (p < q && dist(xmid, md[p]) + len[p] / 2 <= xlen / 2) sl -= len[p] / 2 + dep[md[p]], Add(md[p++], -1);
        Ans += (p - mid - 1ll) * xlen;
        Ans += 1ll * (q - p) * xlen / 2 + sl + 1ll * dep[xmid] * (q - p) - 2 * Query(xmid);
        Ans += sr;
    }
    for (int i = p; i < q; i++) Add(md[i], -1);
    Solve(l, mid);
    Solve(mid + 1, r);
}
signed main() {
    freopen("image.in", "r", stdin);
    freopen("image.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    dep[0] = -1;
    for (int i = 1, u, v; i < n; i++) {
        cin >> u >> v;
        add(u, n + i);
        add(n + i, u);
        add(v, n + i);
        add(n + i, v);
    }
    dfs1(1, 0);
    dfs2(1, 1);
    Solve(1, n);
    cout << Ans / 2 << "\n";
    return 0;
}


有时候可以考虑先不做某一种操作,然后调整。

利用 \(\frac{1}{x - 1} = -\sum\limits_{i = 0}x_i\) 将除法转为无限背包。分圆多项式。

树上圆理论。BIT 维护区间加区间求和。

posted @ 2025-02-09 17:52  forgotmyhandle  阅读(39)  评论(0)    收藏  举报