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 维护区间加区间求和。

浙公网安备 33010602011771号