Exam Records 2
9.6
*B
题目描述
小诗有一个不可重集 \(S\) ,她记得 \(S\) 的元素数量 \(n\) 与 \(\gcd(S)+\operatorname{lcm}(S)\) 的值 \(m\) ,但她已经忘记 \(S\) 由什么元素构成,她想知道有多少种符合条件的构成方案。由于答案过大,你只需告诉她答案 \(\bmod P\) 的值。
对于所有测试点,\(1\le n,m \le 10^9,10^8 \le P \le 10^9+7\),保证 \(P\) 为素数。
赛时读错题,读成 \(n\) 按位与 \(\gcd(S)+\operatorname{lcm}(S)\) 的值为 \(m\)。
枚举 \(\gcd\) 设为 \(g\),\(g \mid m\)。可以计算出 \(\operatorname{lcm}\),设为 \(l\)。
将 \(g,l\) 分解质因数,\(g=\sum p_i^{k_{1,i}}, l = \sum p_i^{k_{2,i}}\)。设对于集合中元素 \(a_x\) 分解质因数为 \(a_x\sum p_i^{t_{x,i}}\)。可知
对于集合中的每个数分解质因子后,每个质因子都有出现次数的上下界。每个质因子出现次数的上下界都至少在一个数中出现过。
容斥解决这个问题。设 \(k_i = k_{2,i} - k_{1,i}\)。对于每个质因子枚举是否卡上下界,共 \(4^{w(l)}\) 种状态。对于上下界都没有限制的情况,其贡献为
虽然 \(n\) 很大,但是 \(\prod(k_i+1)\) 很小,组合数可以计算。
设 \(c\) 为这个状态下为每个质因子上下界限制次数的和。例如钦定不能选择上界,则对 \(c\) 贡献 \(1\);钦定上下界都不能选,则对 \(c\) 贡献 \(2\)。容斥系数是 \((-1)^c\)。
发现可以 \(3^{w(l)}\) 枚举状态,为限制了多少个上下界。设枚举的状态有 \(s\) 个质因子限制了 \(1\) 个上界或下界。则当前状态的贡献乘 \(2^s\)。
代码
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 1000005;
int n, m, ans, mod, cnt, t[20], P[20], fac[N], inv[N], ifa[N];
int C(int x, int y)
{
if(y > x)
return 0;
return fac[x] * ifa[y] % mod * ifa[x - y] % mod;
}
void solve(int x)
{
cnt = 0;
for(int i = 2; i * i <= x; ++ i)
{
if(x % i == 0)
t[++ cnt] = 1;
while(x % i == 0)
x /= i, t[cnt] ++;
}
if(x != 1)
t[++ cnt] = 2;
int res = 0;
for(int i = 0; i < P[cnt]; ++ i)
{
int a = 0, b = 0, c = 0, w = 1;
for(int j = 1; j <= cnt; ++ j)
{
int o = i / P[j - 1] % 3;
if(o == 0)
++ a, w *= t[j];
else if(o == 1)
++ b, w *= t[j] - 1;
else
++ c, w *= t[j] - 2;
}
if(b & 1)
res = (res - C(w, n) * (1 << b) % mod + mod) % mod;
else
res = (res + C(w, n) * (1 << b)) % mod;
}
ans = (ans + res) % mod;
}
signed main()
{
scanf("%lld%lld%lld", &n, &m, &mod);
P[0] = fac[0] = inv[0] = inv[1] = ifa[0] = ifa[1] = 1;
for(int i = 1; i <= 10; ++ i)
P[i] = P[i - 1] * 3;
for(int i = 1; i <= 1e6; ++ i)
fac[i] = fac[i - 1] * i % mod;
for(int i = 2; i <= 1e6; ++ i)
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
for(int i = 2; i <= 1e6; ++ i)
ifa[i] = ifa[i - 1] * inv[i] % mod;
for(int i = 1; i * i <= m; ++ i)
{
if(m % i)
continue;
int A = i, B = m - i;
if(B >= A && B % A == 0)
solve(B / A);
if(i * i == m)
continue;
A = m / i, B = m - A;
if(B >= A && B % A == 0)
solve(B / A);
}
printf("%lld\n", ans);
return 0;
}
可以使用莫反。
设 \(f(k)\) 表示 \(\gcd = 1,\operatorname{lcm} = k\) 的 \(S\) 的个数。\(g(k)\) 表示 \(\gcd = 1,\operatorname{lcm} \mid k\) 的 \(S\) 的个数。
根据转化可知,枚举 \(\gcd\) 后,答案为 \(f(\dfrac {m - g} g)\)。
根据莫反得到:
现在需要求 \(g\)。
设 \(h(k) = {\sum_x[x \mid k] \choose n} = {\sigma_0(k) \choose n}\),则 \(g = \mu * h,f = \mu * \mu * h\)。\(\mu * \mu\) 是积性函数,可以直接计算。最后卷起来即可。
*C
题目描述
定义两个 01 串 \(S,T\) 满足 \(S\to T\) 当且仅当 \(S\) 中值为 \(1\) 的下标集合是 \(T\) 中值为 \(1\) 的下标集合的子集,定义 \(S\leftarrow T\) 当且仅当 \(T\to S\) 。
小诗有一个长为 \(n\) 的 01 串序列 \(s_1,s_2,\cdots,s_n\),其中每个 01 串长度为 \(m\),她想知道有多少子序列 \(1\le p_1<p_2<\cdots<p_k\le n\),使得存在 \(1\le t\le k\) 满足 \(s_{p_1}\to\cdots\to s_{p_t}\leftarrow \cdots\leftarrow s_{p_k}\)。
由于答案过大,你只需告诉她答案 \(\bmod 10^9+7\) 的值。
\(1\le n \le 3\times 10^5,1\le m \le 20\)
上升下降分开考虑。
设 \(f_{i,j}\) 表示到枚举到的串时,前 \(10\) 位为 \(i\),后 \(10\) 位为 \(j\) 的子集的方案数。
注意最后合并的平台。
D
q-analog
9.7
*B
题目描述
void build(int i, int l, int r)
{
L[i] = l;
R[i] = r;
if (l == r)
return;
int mid = (l + r) / 2;
build(i * 2, l, mid);
build(i * 2 + 1, mid + 1, r);
}
对于一棵有 \(n\) 个叶子的线段树,只需要执行 build(1,1,n)
就可以建出线段树。
对于给定的 \(n,x,y\),她想知道满足 \(x\le L_i\le R_i\le y\) 的 \(i\) 的和是多少?
\(1 \le T \le 10^4\),\(1\le x \le y \le n \le 10^{18}\)
赛时读错题,读成节点个数。
先想普通线段树查询一样找到在 \(x,y\) 内,且不被其他在 \(x,y\) 内包含的节点。
设 \(f(x,p)\) 表示长度为 \(x\),标号为 \(p\) 的节点的答案。观察得到 \(f(x,p) = k(x) \times p + b(x)\)。\(k,b\) 可以递推。
C
题目描述
菜汪酱是一名见习魔法师。施展一个魔法需要一根法杖和一个用于施展的咒语,每根法杖和每个咒语都各有两个参数 \(a,b\),施展魔法消耗的魔力定义为 法杖与咒语的 \(a\) 之和 以及 法杖与咒语的 \(b\) 之和 两者的较大值。
每次增加或删除一个咒语或法杖。询问需消耗的最小魔力。强制在线。
考虑咒语 \((a, b)\) 和法杖 \((u, v)\)。若 \(a + u > b + v\),则 \(a - b > v - u\)。
建一棵线段树,维护区间咒语、法杖两个参数的最小值。咒语在 \(a - b\) 处更新,法杖在 \(v - u\) 处更新。每次用左区间 \(b\) 与右区间 \(v\) 的和,左区间 \(u\) 与右区间 \(a\) 的和更新答案。
注意删除操作。
*D
题目描述
菜汪酱在花园里除杂草。花园可以看作一根数轴,有 \(n\) 个位置有杂草,从左到右第 \(i\) 个位置和第 \(i+1\) 个位置间的距离是 \(Di\)。初始时 \(n\) 个位置杂草的高度都是 \(0\),每一丛杂草每秒都会长高 \(1\) 厘米。菜汪酱每秒都能向左或右走一个单位长度,当菜汪酱到达一丛杂草的位置时,她可以立即将其拔掉,之后这丛杂草将不再会生长。菜汪酱 初始时在第 \(k\) 丛杂草的位置,她要将 \(n\) 丛杂草全部拔掉,请你帮她求出她最少需要拔掉总共多少厘米高的杂草?
设 \(s_i\) 为 \(D_i\) 的前缀和。
假如有两个人割草,一个人向左走,一个人向右走,最终答案为 \(\sum_{i=1}^n\lvert s_k - s_i \rvert\)。
对于第 \(i\) 棵草来说,最开始它的贡献是 \(\lvert s_k - s_i \rvert\)。每靠近它一步,它的贡献不变。每远离它一步,它的贡献 \(+ 2\)。
设 \(dp_i\) 表示行走到 \(i\) 位置的答案。
其中 \(i \le k \le j\)。
初始时 \(dp_k = \sum_{i=1}^n\lvert s_k - s_i \rvert\)。最终答案为 \(dp_1\) 或 \(dp_n\)。
使用类似 Dijkstra 的方式转移。
每次找到未被访问过的最小 \(dp_i\) 更新。对于 \(k \le i < j\),有 \(dp_i < dp_j\)。所以每次访问过的位置一定是包含 \(k\) 的一段区间 \([l, r]\)。每次访问过的位置能更新的最小 \(dp_i\) 的位置一定是 \(l - 1\) 或 \(r + 1\)。因此每次比较 \(dp_{l - 1}\) 和 \(dp_{r + 1}\) 即可。
可以单调队列进行斜率优化。
代码
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 2000005;
int n, K, s[N], f[N], q[2][N], head[2], tail[2];
int X(int i)
{
return 2 * i;
}
int Y(int o, int i)
{
if(o == 0)
return f[i] - 2 * i * s[i] + 2 * s[i];
else
return f[i] + 2 * n * s[i] - 2 * i * s[i];
}
int W(int o, int i, int k)
{
if(o == 0)
return f[i] - 2 * i * s[i] + 2 * s[i] - k * 2 * i;
else
return f[i] + 2 * n * s[i] - 2 * i * s[i] - k * 2 * i;
}
double slope(int o, int i, int j)
{
return 1.0 * (Y(o, i) - Y(o, j)) / (X(i) - X(j));
}
signed main()
{
scanf("%lld%lld", &n, &K);
s[1] = 1;
for(int i = 2; i <= n; ++ i)
scanf("%lld", &s[i]), s[i] += s[i - 1];
for(int i = 1; i <= n; ++ i)
f[K] += max(s[K], s[i]) - min(s[K], s[i]);
q[0][1] = q[1][1] = K;
head[0] = tail[0] = head[1] = tail[1] = 1;
for(int l = K, r = K; l != 1 || r != n;)
{
if(r < n)
{
while(head[0] < tail[0] && W(0, q[0][head[0]], - s[r + 1]) > W(0, q[0][head[0] + 1], - s[r + 1]))
++ head[0];
f[r + 1] = W(0, q[0][head[0]], - s[r + 1]) - 2 * s[r + 1];
}
if(l > 1)
{
while(head[1] < tail[1] && W(1, q[1][head[1]], - s[l - 1]) > W(1, q[1][head[1] + 1], - s[l - 1]))
++ head[1];
f[l - 1] = W(1, q[1][head[1]], - s[l - 1]) - 2 * n * s[l - 1];
}
int x = l, y = r;
if(y != n && (x == 1 || f[y + 1] <= f[x - 1]))
{
++ r;
while(head[1] < tail[1] && slope(1, r, q[1][tail[1]]) < slope(1, r, q[1][tail[1] - 1]))
-- tail[1];
q[1][++ tail[1]] = r;
}
if(x != 1 && (y == n || f[x - 1] <= f[y + 1]))
{
-- l;
while(head[0] < tail[0] && slope(0, l, q[0][tail[0]]) > slope(0, l, q[0][tail[0] - 1]))
-- tail[0];
q[0][++ tail[0]] = l;
}
}
printf("%lld\n", min(f[1], f[n]));
return 0;
}
9.14
难度 B < A < C。
A
题目描述
在之后的第 \(i\) 个月中,他需要花费恰好 \(c_i\) 个钻石进行招募。
然而钻石并不是白给的,必须通过氪金才能获得。PCR 为玩家提供了 \(m\) 种不同的购买方案,每种购买方案用两个整数 \((a_i,b_i)\), 表示,表示这种方案中钻石的价格为 \(a_i\) 元一个,每个月最多用这种购买方案购买 \(b_i\) 个钻石。若 \(b_i=−1\),则表示这种购买方案没有次数限制。注意一个月中可以使用多种不同的购买方案进行购买,另外不同的月之间购买方案的种类并不会变。注意虽然第 \(i\) 个月需要花费恰好 \(c_i\) 个钻石,但是可以购买的量不受限制,可以购买超过 \(c_i\) 的钻石,留到之后的月用。
小 \(W\) 想要知道他需要在接下来的 \(n\) 个月中最少氪多少元才能够完成招募。
赛时想了对于每天考虑或者对于每个钻石考虑的思路。写了对于每个钻石考虑跑不满的暴力。
正难则反。对于每一天考虑。把钻石按照价格排序后,每天买的钻石是一个前缀。每天考虑把所有能买的钻石加入库存,加入库存不消耗代价。使用钻石时从库存中取出,此时消耗代价。这个过程可以使用线段树维护。
代码
#include <cstdio>
#include <algorithm>
#define ls p << 1
#define rs p << 1 | 1
#define int long long
using namespace std;
const int N = 200005;
int n, m, ans, c[N], pre[N], val[N];
struct Node
{
int a, b;
};
Node a[N];
struct Tree
{
int l, r, sum, res, k, b, B, cov;
};
Tree s[N << 2];
void build(int p, int l, int r)
{
s[p].l = l;
s[p].r = r;
if(l == r)
return;
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
}
void tag(int p, int k, int b, int B)
{
s[p].k += k;
s[p].b += b;
s[p].B += B;
s[p].sum += k * (pre[s[p].r] - pre[s[p].l - 1]) + b;
s[p].res += k * (val[s[p].r] - val[s[p].l - 1]) + B;
}
void push_down(int p)
{
if(s[p].cov)
{
s[ls].sum = s[ls].res = s[ls].k = s[ls].b = s[ls].B = 0;
s[rs].sum = s[rs].res = s[rs].k = s[rs].b = s[rs].B = 0;
s[ls].cov = s[rs].cov = 1;
s[p].cov = 0;
}
tag(ls, s[p].k, s[p].b, s[p].B);
tag(rs, s[p].k, s[p].b, s[p].B);
s[p].k = s[p].b = s[p].B = 0;
}
void query(int p, int k)
{
if(s[p].l == s[p].r)
{
ans += k * a[s[p].l].a;
s[p].sum -= k;
s[p].res -= k * a[s[p].l].a;
return;
}
push_down(p);
int mid = s[p].l + s[p].r >> 1;
if(s[ls].sum >= k)
query(ls, k);
else
{
ans += s[ls].res;
query(rs, k - s[ls].sum);
s[ls].cov = 1;
s[ls].sum = s[ls].res = s[ls].k = s[ls].b = s[ls].B = 0;
}
s[p].sum = s[ls].sum + s[rs].sum;
s[p].res = s[ls].res + s[rs].res;
}
signed main()
{
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; ++ i)
scanf("%lld", &c[i]);
for(int i = 1; i <= m; ++ i)
{
scanf("%lld%lld", &a[i].a, &a[i].b);
if(a[i].b == -1)
a[i].b = 1e6;
}
sort(a + 1, a + 1 + m, [](Node i, Node j)
{ return i.a < j.a; });
for(int i = 1; i <= m; ++ i)
pre[i] = pre[i - 1] + a[i].b, val[i] = val[i - 1] + a[i].a * a[i].b;
build(1, 1, m);
for(int i = 1; i <= n; ++ i)
{
s[1].k ++;
s[1].sum += pre[m];
s[1].res += val[m];
query(1, c[i]);
}
printf("%lld\n", ans);
return 0;
}
B
对于两个字符串 \(a, b\),定义 \(a \le b\) 为 \(a + b\) 的字典序小于等于 \(b + a\) 的字典序。这是偏序关系。
*C
题目描述
有 \(n\) 堆石子,第 \(i\) 堆石子有 \(x_i\) 个,价值 \(y_i\)。小 \(W\) 选出一些石子堆的子集 \(S\),然后每个石子堆复制一份。现在有 \(2\lvert S \rvert\) 堆石子。对方选择任意堆数任意物品删除,不能全部删除。小 \(W\) 先手,轮流选择一堆或两堆石子,拿走任意颗石子(如果选择两堆,两堆拿走的石子可以不同)。不能操作者输。如果获胜,小 \(W\) 获得 \(\sum_{i \in S} y_i\) 的价值。否则,获得 \(0\) 的价值。
对于每个 \(i\),求仅使用前 \(i\) 堆石子的价值。
看清时对手随意删除一些石子堆。
如果有 \(n\) 堆石子,每次从一堆或两堆取石子,问先手胜还是后手胜。
对于 \(a_i\),转成二进制,做三进制不进位加法。先手必胜当且仅当答案不为 \(0\)。
如果当前局面不为 \(0\),先手总能操作使得对手得到的局面为 \(0\)。
把题目中石子大小的二进制称为向量 \(a_1,\cdots, a_n\)。如果后手必胜为存在不全是 \(0\) 的 \(k_1,\cdots,k_n\) 三进制加法满足
如果先手必胜,相当于 \(a_1,\cdots,a_n\) 在模 \(3\) 意义下线性无关。
使用线性基维护。注意每次插入新向量时,如果新向量的价值比线性基中当前位置的价值大,则更改当前位置的值。然后继续将原来线性基的值插入。
使用两个二进制数,一个维护当前位置三进制是否是 \(1\),一个维护三进制是否时 \(2\)。
代码
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 500005;
const int K = (1ll << 61) - 1;
int n, ans, y[N], A[65], B[65], p[65], f[65], g[65];
void add(int &a, int &b, int x, int y)
{
int u = a, v = b;
a = (v & y) | (u & (K ^ (x | y))) | (x & (K ^ (u | v)));
b = (u & x) | (v & (K ^ (x | y))) | (y & (K ^ (u | v)));
}
void sub(int &a, int &b, int x, int y)
{
int u = a, v = b;
a = (v & x) | ((K ^ (u | v)) & y) | (u & (K ^ (x | y)));
b = (u & y) | ((K ^ (u | v)) & x) | (v & (K ^ (x | y)));
}
void ins(int a, int b, int id)
{
int u = 0, v = 0;
for(int i = 60; i >= 0; -- i)
{
if(!(a >> i & 1) && !(b >> i & 1))
continue;
if(!p[i])
{
A[i] = a;
B[i] = b;
p[i] = id;
ans += y[id];
break;
}
if(y[p[i]] < y[id])
{
ans -= y[p[i]];
ans += y[id];
swap(p[i], id);
swap(a, A[i]);
swap(b, B[i]);
}
if(!p[i])
continue;
if(((a >> i & 1) && (A[i] >> i & 1)) || ((b >> i & 1) && (B[i] >> i & 1)))
sub(a, b, A[i], B[i]);
else
add(a, b, A[i], B[i]);
}
}
signed main()
{
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
scanf("%lld", &n);
for(int i = 1, x, a, b; i <= n; ++ i)
{
scanf("%lld%lld", &x, &y[i]);
ins(x ^ ans, 0, i);
printf("%lld\n", ans);
}
return 0;
}
9.20
*B
题目描述
给定 \(n\) 个物品, 第 \(i\) 个物品有重量 \(w_i\)。这是一个特殊的世界,物品的质量可以是负数。
你将随意地取出这 \(n\) 个物品中的若干个,计算所有物品的重量然后搬走,正常的 oi 选手应该都知道这样的方案数有 \(2n\) 种(允许什么都不取和全部取)。
你很懒,你不想搬太大的重量,但是也不好意思什么都不搬,于是你想找到所有方案中总重量前 \(k\) 小的方案来供你选择。
首先设 \(sum = \sum_{a_i < 0} a_i\)。然后把 \(a_i < 0\) 的位置取反。
对于一个方案 \((val, num)\) 表示考虑到第 \(num\) 个元素,当前花费为 \(val\)。用堆维护可选方案。每次可以更新 \((val + a_{num + 1}, num + 1)\) 和 \((val - a_{num} + a_{num + 1}, num + 1)\)。每次更新后的 \(val\) 一定不小于原 \(val\),且能生成 \(1 + 2 + 4 + \cdots + 2 ^{n - 1}\) 个方案。再加上最开始选所有负数的方案,可以做到不重不漏。
C
矩阵有一些点,询问矩形内有多少行有点。
三维偏序,可以树套树或 cdq。
可以莫队分块。
9.23
C
形式化题意:平面上有 \(n\) 个点,你要找到一个最小的 \(r\) 使得每个点为圆心画半径为 \(r\) 的圆,存在一条直线经过所有圆(相切也算经过)。
旋转卡壳,求两条线之间距离。
对于两条平行直线 \(Ax+By+C_1=0\) 和 \(Ax+By+C_2 = 0\)。
距离为
点线距
D
题目描述
定义
有一个长度为 \(n\) 的数组 \(a_1,\cdots,a_n\),有 \(m\) 次操作,操作分为修改和询问。
修改分为四种:
- 给出 \(l,r,x\) 表示所有 \(l \le i \le r\) 的 \(a_i\) 赋值为 \(x\);
- 给出 \(l,r,x\) 表示所有 \(l \le i \le r\) 的 \(a_i\) 赋值成 \(\lvert a_i \rvert x\),其中 \(x=\pm 1\);
- 给出 \(l,r,x\) 表示所有 \(l \le i \le r\) 的 \(a_i\) 赋值成 \(a_i\),其中 \(x=\pm 1\);
- 给出 \(l,r,x\) 表示所有 \(l \le i \le r\) 的 \(a_i\) 赋值成 \(\operatorname{sgn}(a_i)x\)。
查询的形式为给出 \(l,r\)。定义一个区间 \([L,R]\) 是合法的当且仅当 \(l \le L \le R \le r\) 并且 \(a_L>0,a_R>0\),定义一个区间 \([L,R]\) 是极长的当且仅当 \([L,R]\) 合法,\([L,R+1]\) 和 \([L−1,R]\) 都不合法。定义一个极长区间的权值是所有区间内位置的权值和,定义一个位置的权值是所有能抵达的位置的 \(a_i\) 的积,如果 \(i\) 能抵达 \(j\) 当且仅当 \(l\le i,j\le r,\forall \min(i,j)\le k\le \max(i,j),a_k>0\)。对于非正整数位置,权值为 \(0\)。你需要求出所有极长区间的权值和。对 \(2^{32}\) 取模。
保证值域为 \([−10^9,10^9] \setminus \{0\}\)。
操作 \(1,2,3\) 直接用线段树维护,注意因为有取反操作,所以正号连续段和负号连续段各自分别维护。考虑操作 \(4\),长度为 \(len\) 的区间最多只有 \(\sqrt{len}\) 个本质不同的连续段。每个节点上维护长度为 \(x\) 的连续段的各种系数。每次修改是 \(\sqrt 1 + \sqrt 2 + \sqrt 4 + \cdots + \sqrt n\) 的。
注意 unordered_map 常数很大。我写的正解不如暴力跑的快。
代码
暴力做 \(4\) 操作。
#include <cstdio>
#include <algorithm>
#define ls p << 1
#define rs p << 1 | 1
#define int long long
using namespace std;
const int N = 100005;
int n, m, b[N];
unsigned a[N];
int Abs(int x)
{
return x > 0 ? x : -x;
}
int Sgn(int x)
{
return x > 0 ? 1 : (x < 0 ? -1 : 0);
}
unsigned Pow(unsigned x, int y)
{
unsigned r = 1;
for(; y; y >>= 1, x = x * x)
if(y & 1)
r = r * x;
return r;
}
struct Tree
{
int l, r, sgn, rev;
unsigned cov, mul, num[2], sum[2], res[2], pre[2], suf[2], val[2][2], len[2][2];
};
Tree s[N << 2];
Tree operator + (Tree x, Tree y)
{
Tree a;
a.l = x.l;
a.r = y.r;
a.sgn = a.rev = a.cov = 0;
a.mul = x.mul * y.mul;
for(int o = 0; o < 2; ++ o)
{
if(x.len[o][1] && y.len[o][0])
{
a.num[o] = x.num[o] + y.num[o] - 1;
a.sum[o] = x.sum[o] - x.val[o][1] * x.len[o][1] + y.sum[o] - y.val[o][0] * y.len[o][0] + x.val[o][1] * y.val[o][0] * (x.len[o][1] + y.len[o][0]);
a.res[o] = x.res[o] - x.val[o][1] * x.len[o][1] * x.num[o]
+ y.res[o] - y.val[o][0] * y.len[o][0] * y.num[o]
+ (x.pre[o] - x.val[o][1] * x.len[o][1] * x.num[o]) * (y.num[o] - 1)
+ (y.suf[o] - y.val[o][0] * y.len[o][0] * y.num[o]) * (x.num[o] - 1)
+ x.val[o][1] * y.val[o][0] * (x.len[o][1] + y.len[o][0]) * x.num[o] * y.num[o];
a.pre[o] = x.pre[o] - x.val[o][1] * x.len[o][1] * x.num[o]
+ y.pre[o] - y.val[o][0] * y.len[o][0]
+ (y.sum[o] - y.val[o][0] * y.len[o][0]) * (x.num[o] - 1)
+ x.val[o][1] * y.val[o][0] * (x.len[o][1] + y.len[o][0]) * x.num[o];
a.suf[o] = y.suf[o] - y.val[o][0] * y.len[o][0] * y.num[o]
+ x.suf[o] - x.val[o][1] * x.len[o][1]
+ (x.sum[o] - x.val[o][1] * x.len[o][1]) * (y.num[o] - 1)
+ x.val[o][1] * y.val[o][0] * (x.len[o][1] + y.len[o][0]) * y.num[o];
if(x.len[o][0] != x.r - x.l + 1)
a.len[o][0] = x.len[o][0], a.val[o][0] = x.val[o][0];
else
a.len[o][0] = x.len[o][0] + y.len[o][0], a.val[o][0] = x.val[o][0] * y.val[o][0];
if(y.len[o][1] != y.r - y.l + 1)
a.len[o][1] = y.len[o][1], a.val[o][1] = y.val[o][1];
else
a.len[o][1] = y.len[o][1] + x.len[o][1], a.val[o][1] = x.val[o][1] * y.val[o][1];
continue;
}
a.num[o] = x.num[o] + y.num[o];
a.sum[o] = x.sum[o] + y.sum[o];
a.res[o] = x.res[o] + y.res[o] + x.pre[o] * y.num[o] + x.num[o] * y.suf[o];
a.pre[o] = x.pre[o] + y.pre[o] + y.sum[o] * x.num[o];
a.suf[o] = y.suf[o] + x.suf[o] + x.sum[o] * y.num[o];
a.val[o][0] = x.val[o][0];
a.val[o][1] = y.val[o][1];
a.len[o][0] = x.len[o][0];
a.len[o][1] = y.len[o][1];
}
return a;
}
void Rev(int p)
{
s[p].rev ^= 1;
s[p].sgn = - s[p].sgn;
swap(s[p].num[0], s[p].num[1]);
swap(s[p].sum[0], s[p].sum[1]);
swap(s[p].res[0], s[p].res[1]);
swap(s[p].pre[0], s[p].pre[1]);
swap(s[p].suf[0], s[p].suf[1]);
swap(s[p].val[0][0], s[p].val[1][0]);
swap(s[p].val[0][1], s[p].val[1][1]);
swap(s[p].len[0][0], s[p].len[1][0]);
swap(s[p].len[0][1], s[p].len[1][1]);
}
void Cov(int p, int a, int b)
{
s[p].cov = a;
s[p].sgn = b;
int o = max(b, 0ll);
s[p].num[o] = 1;
s[p].len[o][0] = s[p].len[o][1] = s[p].r - s[p].l + 1;
s[p].mul = s[p].val[o][0] = s[p].val[o][1] = Pow(a, s[p].r - s[p].l + 1);
s[p].sum[o] = s[p].res[o] = s[p].pre[o] = s[p].suf[o] = s[p].mul * (s[p].r - s[p].l + 1);
o = !o;
s[p].num[o] = s[p].sum[o] = s[p].res[o] = s[p].pre[o] = s[p].suf[o] = s[p].val[o][0] = s[p].val[o][1] = s[p].len[o][0] = s[p].len[o][1] = 0;
}
void Sign(int p, int x)
{
s[p].sgn = x;
int o = max(x, 0ll);
s[p].num[o] = 1;
s[p].len[o][0] = s[p].len[o][1] = s[p].r - s[p].l + 1;
s[p].val[o][0] = s[p].val[o][1] = s[p].mul;
s[p].sum[o] = s[p].res[o] = s[p].pre[o] = s[p].suf[o] = s[p].mul * (s[p].r - s[p].l + 1);
o = !o;
s[p].num[o] = s[p].sum[o] = s[p].res[o] = s[p].pre[o] = s[p].suf[o] = s[p].val[o][0] = s[p].val[o][1] = s[p].len[o][0] = s[p].len[o][1] = 0;
}
void push_down(int p)
{
if(s[p].rev)
{
Rev(ls);
Rev(rs);
s[p].rev = 0;
}
if(s[p].cov)
{
Cov(ls, s[p].cov, s[p].sgn);
Cov(rs, s[p].cov, s[p].sgn);
s[p].cov = s[p].sgn = 0;
}
if(s[p].sgn)
{
Sign(ls, s[p].sgn);
Sign(rs, s[p].sgn);
s[p].sgn = 0;
}
}
void build(int p, int l, int r)
{
s[p].l = l;
s[p].r = r;
if(l == r)
{
if(b[l] != 0)
{
int o = max(b[l], 0ll);
s[p].num[o] = s[p].len[o][0] = s[p].len[o][1] = 1;
s[p].sum[o] = s[p].res[o] = s[p].pre[o] = s[p].suf[o] = s[p].val[o][0] = s[p].val[o][1] = s[p].mul = a[l];
}
return;
}
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
s[p] = s[ls] + s[rs];
}
void updCov(int p, int l, int r, int x, int y)
{
if(s[p].l >= l && s[p].r <= r)
return Cov(p, x, y);
push_down(p);
int mid = s[p].l + s[p].r >> 1;
if(l <= mid)
updCov(ls, l, r, x, y);
if(r > mid)
updCov(rs, l, r, x, y);
s[p] = s[ls] + s[rs];
}
void updSgn(int p, int l, int r, int x)
{
if(s[p].l >= l && s[p].r <= r)
return Sign(p, x);
push_down(p);
int mid = s[p].l + s[p].r >> 1;
if(l <= mid)
updSgn(ls, l, r, x);
if(r > mid)
updSgn(rs, l, r, x);
s[p] = s[ls] + s[rs];
}
void updRev(int p, int l, int r)
{
if(s[p].l >= l && s[p].r <= r)
return Rev(p);
push_down(p);
int mid = s[p].l + s[p].r >> 1;
if(l <= mid)
updRev(ls, l, r);
if(r > mid)
updRev(rs, l, r);
s[p] = s[ls] + s[rs];
}
void updVal(int p, int l, int r, int x, int y)
{
if(s[p].l >= l && s[p].r <= r)
{
if(s[p].len[0][0] == s[p].r - s[p].l + 1)
return Cov(p, x, - y);
if(s[p].len[1][0] == s[p].r - s[p].l + 1)
return Cov(p, x, y);
}
push_down(p);
int mid = s[p].l + s[p].r >> 1;
if(l <= mid)
updVal(ls, l, r, x, y);
if(r > mid)
updVal(rs, l, r, x, y);
s[p] = s[ls] + s[rs];
}
Tree get(int p, int l, int r)
{
if(s[p].l >= l && s[p].r <= r)
return s[p];
push_down(p);
int mid = s[p].l + s[p].r >> 1;
if(r <= mid)
return get(ls, l, r);
if(l > mid)
return get(rs, l, r);
return get(ls, l, r) + get(rs, l, r);
}
signed main()
{
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
scanf("%lld%lld", &n, &m);
for(int i = 1, x; i <= n; ++ i)
scanf("%lld", &x), a[i] = Abs(x), b[i] = Sgn(x);
build(1, 1, n);
for(int i = 1, o, l, r, x; i <= m; ++ i)
{
scanf("%lld%lld%lld", &o, &l, &r);
if(o != 5)
scanf("%lld", &x);
if(o == 1)
updCov(1, l, r, Abs(x), Sgn(x));
if(o == 2)
updSgn(1, l, r, Sgn(x));
if(o == 3 && x < 0)
updRev(1, l, r);
if(o == 4)
updVal(1, l, r, Abs(x), Sgn(x));
if(o == 5)
printf("%u\n", get(1, l, r).res[1]);
o = l + r;
}
return 0;
}
过不去的正解。
#include <cstdio>
#include <vector>
#include <algorithm>
#include <unordered_map>
#define ls p << 1
#define rs p << 1 | 1
using namespace std;
const int N = 100005;
int n, m, b[N];
unsigned a[N];
unsigned I[131][100001], J[131][100001];
inline int Abs(int x)
{
return x > 0 ? x : -x;
}
inline int Sgn(int x)
{
return x > 0 ? 1 : (x < 0 ? -1 : 0);
}
unsigned Pow(unsigned x, int y)
{
if(y < 16900)
return I[y % 130][x] * J[y / 130][x];
unsigned r = 1;
for(x = I[1][x]; y; y >>= 1, x = x * x)
if(y & 1)
r = r * x;
return r;
}
struct Tree
{
int l, r, sgn, rev;
unsigned cov, mul, num[2], sum[2], res[2], pre[2], suf[2], val[2][2], len[2][2];
vector < pair<int, unsigned> > T[2], L[2], R[2], C[2];
};
Tree s[N << 2];
unsigned U[N], V[N], H[N];
Tree operator + (Tree x, Tree y)
{
Tree a;
a.l = x.l;
a.r = y.r;
a.sgn = a.rev = a.cov = 0;
a.mul = x.mul * y.mul;
for(int o = 0; o < 2; ++ o)
{
if(x.len[o][1] && y.len[o][0])
{
unsigned X = x.len[o][1], Y = y.len[o][0];
unsigned P = x.val[o][1], Q = y.val[o][0];
unsigned XW = x.pre[o] - P * x.len[o][1] * x.num[o], YW = y.suf[o] - Q * y.len[o][0] * y.num[o];
a.num[o] = x.num[o] + y.num[o] - 1;
a.sum[o] = x.sum[o] - P * X + y.sum[o] - Q * Y + P * Q * (X + Y);
a.res[o] = x.res[o] - P * X * x.num[o]
+ y.res[o] - Q * Y * y.num[o]
+ XW * (y.num[o] - 1)
+ YW * (x.num[o] - 1)
+ P * Q * (X + Y) * x.num[o] * y.num[o];
a.pre[o] = XW
+ y.pre[o] - Q * Y
+ (y.sum[o] - Q * Y) * (x.num[o] - 1)
+ P * Q * (X + Y) * x.num[o];
a.suf[o] = YW
+ x.suf[o] - P * X
+ (x.sum[o] - P * X) * (y.num[o] - 1)
+ P * Q * (X + Y) * y.num[o];
if(x.len[o][0] != x.r - x.l + 1)
a.len[o][0] = x.len[o][0], a.val[o][0] = x.val[o][0];
else
a.len[o][0] = x.len[o][0] + Y, a.val[o][0] = x.val[o][0] * Q;
if(y.len[o][1] != y.r - y.l + 1)
a.len[o][1] = y.len[o][1], a.val[o][1] = y.val[o][1];
else
a.len[o][1] = y.len[o][1] + X, a.val[o][1] = P * y.val[o][1];
for(auto u : x.L[o])
U[u.first] = u.second;
for(auto u : y.R[o])
V[u.first] = u.second;
for(auto u : x.T[o])
H[u.first] += u.second + U[u.first] * (y.num[o] - 1);
for(auto u : y.T[o])
H[u.first] += u.second + V[u.first] * (x.num[o] - 1);
H[X] -= x.num[o] * y.num[o];
H[Y] -= x.num[o] * y.num[o];
H[X + Y] += x.num[o] * y.num[o];
for(auto u : x.T[o])
if(H[u.first])
a.T[o].push_back(make_pair(u.first, H[u.first])), H[u.first] = 0;
for(auto u : y.T[o])
if(H[u.first])
a.T[o].push_back(make_pair(u.first, H[u.first])), H[u.first] = 0;
if(H[X + Y])
a.T[o].push_back(make_pair(X + Y, H[X + Y])), H[X + Y] = 0;
for(auto u : y.C[o])
U[u.first] = u.second;
for(auto u : x.L[o])
H[u.first] += u.second;
for(auto u : y.L[o])
H[u.first] += u.second + (x.num[o] - 1) * U[u.first];
H[X] -= x.num[o];
H[Y] -= x.num[o];
H[X + Y] += x.num[o];
for(auto u : x.L[o])
if(H[u.first])
a.L[o].push_back(make_pair(u.first, H[u.first])), H[u.first] = 0;
for(auto u : y.L[o])
if(H[u.first])
a.L[o].push_back(make_pair(u.first, H[u.first])), H[u.first] = 0;
if(H[X + Y])
a.L[o].push_back(make_pair(X + Y, H[X + Y])), H[X + Y] = 0;
for(auto u : x.C[o])
U[u.first] = u.second;
for(auto u : x.R[o])
H[u.first] += u.second + (y.num[o] - 1) * U[u.first];
for(auto u : y.R[o])
H[u.first] += u.second;
H[X] -= y.num[o];
H[Y] -= y.num[o];
H[X + Y] += y.num[o];
for(auto u : x.R[o])
if(H[u.first])
a.R[o].push_back(make_pair(u.first, H[u.first])), H[u.first] = 0;
for(auto u : y.R[o])
if(H[u.first])
a.R[o].push_back(make_pair(u.first, H[u.first])), H[u.first] = 0;
if(H[X + Y])
a.R[o].push_back(make_pair(X + Y, H[X + Y])), H[X + Y] = 0;
for(auto u : x.C[o])
H[u.first] += u.second;
for(auto u : y.C[o])
H[u.first] += u.second;
-- H[X];
-- H[Y];
H[X + Y] ++;
for(auto u : x.C[o])
if(H[u.first])
a.C[o].push_back(make_pair(u.first, H[u.first])), H[u.first] = 0;
for(auto u : y.C[o])
if(H[u.first])
a.C[o].push_back(make_pair(u.first, H[u.first])), H[u.first] = 0;
if(H[X + Y])
a.C[o].push_back(make_pair(X + Y, H[X + Y])), H[X + Y] = 0;
continue;
}
a.num[o] = x.num[o] + y.num[o];
a.sum[o] = x.sum[o] + y.sum[o];
a.res[o] = x.res[o] + y.res[o] + x.pre[o] * y.num[o] + x.num[o] * y.suf[o];
a.pre[o] = x.pre[o] + y.pre[o] + y.sum[o] * x.num[o];
a.suf[o] = y.suf[o] + x.suf[o] + x.sum[o] * y.num[o];
a.val[o][0] = x.val[o][0];
a.val[o][1] = y.val[o][1];
a.len[o][0] = x.len[o][0];
a.len[o][1] = y.len[o][1];
for(auto u : x.L[o])
U[u.first] = u.second;
for(auto u : y.R[o])
V[u.first] = u.second;
for(auto u : x.T[o])
H[u.first] += u.second + U[u.first] * y.num[o];
for(auto u : y.T[o])
H[u.first] += u.second + x.num[o] * V[u.first];
for(auto u : x.T[o])
if(H[u.first])
a.T[o].push_back({u.first, H[u.first]}), H[u.first] = 0;
for(auto u : y.T[o])
if(H[u.first])
a.T[o].push_back({u.first, H[u.first]}), H[u.first] = 0;
for(auto u : y.C[o])
U[u.first] = u.second;
for(auto u : x.L[o])
H[u.first] += u.second;
for(auto u : y.L[o])
H[u.first] += u.second + x.num[o] * U[u.first];
for(auto u : x.L[o])
if(H[u.first])
a.L[o].push_back({u.first, H[u.first]}), H[u.first] = 0;
for(auto u : y.L[o])
if(H[u.first])
a.L[o].push_back({u.first, H[u.first]}), H[u.first] = 0;
for(auto u : x.C[o])
U[u.first] = u.second;
for(auto u : x.R[o])
H[u.first] += u.second + U[u.first] * y.num[o];
for(auto u : y.R[o])
H[u.first] += u.second;
for(auto u : x.R[o])
if(H[u.first])
a.R[o].push_back({u.first, H[u.first]}), H[u.first] = 0;
for(auto u : y.R[o])
if(H[u.first])
a.R[o].push_back({u.first, H[u.first]}), H[u.first] = 0;
for(auto u : x.C[o])
H[u.first] += u.second;
for(auto u : y.C[o])
H[u.first] += u.second;
for(auto u : x.C[o])
if(H[u.first])
a.C[o].push_back({u.first, H[u.first]}), H[u.first] = 0;
for(auto u : y.C[o])
if(H[u.first])
a.C[o].push_back({u.first, H[u.first]}), H[u.first] = 0;
}
return a;
}
inline void Rev(int p)
{
s[p].rev ^= 1;
s[p].sgn = - s[p].sgn;
swap(s[p].num[0], s[p].num[1]);
swap(s[p].sum[0], s[p].sum[1]);
swap(s[p].res[0], s[p].res[1]);
swap(s[p].pre[0], s[p].pre[1]);
swap(s[p].suf[0], s[p].suf[1]);
swap(s[p].val[0][0], s[p].val[1][0]);
swap(s[p].val[0][1], s[p].val[1][1]);
swap(s[p].len[0][0], s[p].len[1][0]);
swap(s[p].len[0][1], s[p].len[1][1]);
swap(s[p].T[0], s[p].T[1]);
swap(s[p].L[0], s[p].L[1]);
swap(s[p].R[0], s[p].R[1]);
swap(s[p].C[0], s[p].C[1]);
}
inline void Cov(int p, unsigned a, int b)
{
s[p].T[0].clear(), s[p].T[1].clear();
s[p].L[0].clear(), s[p].L[1].clear();
s[p].R[0].clear(), s[p].R[1].clear();
s[p].C[0].clear(), s[p].C[1].clear();
s[p].cov = a;
s[p].sgn = b;
int o = max(b, 0);
s[p].num[o] = 1;
s[p].len[o][0] = s[p].len[o][1] = s[p].r - s[p].l + 1;
s[p].mul = s[p].val[o][0] = s[p].val[o][1] = Pow(a, s[p].r - s[p].l + 1);
s[p].sum[o] = s[p].res[o] = s[p].pre[o] = s[p].suf[o] = s[p].mul * (s[p].r - s[p].l + 1);
pair <int, unsigned> w = make_pair(s[p].r - s[p].l + 1, 1);
s[p].T[o].push_back(w);
s[p].L[o].push_back(w);
s[p].R[o].push_back(w);
s[p].C[o].push_back(w);
o = !o;
s[p].num[o] = s[p].sum[o] = s[p].res[o] = s[p].pre[o] = s[p].suf[o] = s[p].val[o][0] = s[p].val[o][1] = s[p].len[o][0] = s[p].len[o][1] = 0;
}
inline void Sign(int p, int x)
{
s[p].T[0].clear(), s[p].T[1].clear();
s[p].L[0].clear(), s[p].L[1].clear();
s[p].R[0].clear(), s[p].R[1].clear();
s[p].C[0].clear(), s[p].C[1].clear();
s[p].sgn = x;
int o = max(x, 0);
s[p].num[o] = 1;
s[p].len[o][0] = s[p].len[o][1] = s[p].r - s[p].l + 1;
s[p].val[o][0] = s[p].val[o][1] = s[p].mul;
s[p].sum[o] = s[p].res[o] = s[p].pre[o] = s[p].suf[o] = s[p].mul * (s[p].r - s[p].l + 1);
pair <int, unsigned> w = make_pair(s[p].r - s[p].l + 1, 1);
s[p].T[o].push_back(w);
s[p].L[o].push_back(w);
s[p].R[o].push_back(w);
s[p].C[o].push_back(w);
o = !o;
s[p].num[o] = s[p].sum[o] = s[p].res[o] = s[p].pre[o] = s[p].suf[o] = s[p].val[o][0] = s[p].val[o][1] = s[p].len[o][0] = s[p].len[o][1] = 0;
}
inline void Val(int p, unsigned a)
{
s[p].cov = a;
s[p].mul = Pow(a, s[p].r - s[p].l + 1);
for(int o = 0; o < 2; ++ o)
{
s[p].val[o][0] = s[p].len[o][0] ? Pow(a, s[p].len[o][0]) : 0;
s[p].val[o][1] = s[p].len[o][1] ? Pow(a, s[p].len[o][1]) : 0;
s[p].num[o] = s[p].res[o] = s[p].sum[o] = s[p].pre[o] = s[p].suf[o] = 0;
for(auto u : s[p].T[o])
H[u.first] = Pow(a, u.first) * u.first, s[p].res[o] += u.second * H[u.first];
for(auto u : s[p].L[o])
s[p].pre[o] += u.second * H[u.first];
for(auto u : s[p].R[o])
s[p].suf[o] += u.second * H[u.first];
for(auto u : s[p].C[o])
s[p].sum[o] += u.second * H[u.first], s[p].num[o] += u.second, H[u.first] = 0;
}
}
inline void push_down(int p)
{
if(s[p].rev)
{
Rev(ls);
Rev(rs);
s[p].rev = 0;
}
if(s[p].cov && s[p].sgn)
{
Cov(ls, s[p].cov, s[p].sgn);
Cov(rs, s[p].cov, s[p].sgn);
s[p].cov = s[p].sgn = 0;
}
if(s[p].cov)
{
Val(ls, s[p].cov);
Val(rs, s[p].cov);
s[p].cov = 0;
}
if(s[p].sgn)
{
Sign(ls, s[p].sgn);
Sign(rs, s[p].sgn);
s[p].sgn = 0;
}
}
void build(int p, int l, int r)
{
s[p].l = l;
s[p].r = r;
if(l == r)
{
if(b[l] != 0)
{
int o = max(b[l], 0);
pair <int, unsigned> w = make_pair(1, 1);
s[p].T[o].push_back(w);
s[p].L[o].push_back(w);
s[p].R[o].push_back(w);
s[p].C[o].push_back(w);
s[p].num[o] = s[p].len[o][0] = s[p].len[o][1] = 1;
s[p].sum[o] = s[p].res[o] = s[p].pre[o] = s[p].suf[o] = s[p].val[o][0] = s[p].val[o][1] = s[p].mul = a[l];
}
return;
}
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
s[p] = s[ls] + s[rs];
}
void updCov(int p, int l, int r, int x, int y)
{
if(s[p].l >= l && s[p].r <= r)
return Cov(p, x, y);
push_down(p);
int mid = s[p].l + s[p].r >> 1;
if(l <= mid)
updCov(ls, l, r, x, y);
if(r > mid)
updCov(rs, l, r, x, y);
s[p] = s[ls] + s[rs];
}
void updSgn(int p, int l, int r, int x)
{
if(s[p].l >= l && s[p].r <= r)
return Sign(p, x);
push_down(p);
int mid = s[p].l + s[p].r >> 1;
if(l <= mid)
updSgn(ls, l, r, x);
if(r > mid)
updSgn(rs, l, r, x);
s[p] = s[ls] + s[rs];
}
void updRev(int p, int l, int r)
{
if(s[p].l >= l && s[p].r <= r)
return Rev(p);
push_down(p);
int mid = s[p].l + s[p].r >> 1;
if(l <= mid)
updRev(ls, l, r);
if(r > mid)
updRev(rs, l, r);
s[p] = s[ls] + s[rs];
}
void updVal(int p, int l, int r, int x, int y)
{
if(s[p].l >= l && s[p].r <= r)
{
Val(p, x);
if(y == -1)
Rev(p);
return;
}
push_down(p);
int mid = s[p].l + s[p].r >> 1;
if(l <= mid)
updVal(ls, l, r, x, y);
if(r > mid)
updVal(rs, l, r, x, y);
s[p] = s[ls] + s[rs];
}
Tree get(int p, int l, int r)
{
if(s[p].l >= l && s[p].r <= r)
return s[p];
push_down(p);
int mid = s[p].l + s[p].r >> 1;
if(r <= mid)
return get(ls, l, r);
if(l > mid)
return get(rs, l, r);
return get(ls, l, r) + get(rs, l, r);
}
void init(int y, unsigned x)
{
I[0][y] = 1;
for(int i = 1; i <= 130; ++ i)
I[i][y] = I[i - 1][y] * x;
J[0][y] = 1;
for(int i = 1; i <= 130; ++ i)
J[i][y] = J[i - 1][y] * I[130][y];
}
int idx;
int main()
{
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
scanf("%d%d", &n, &m);
for(int i = 1, x; i <= n; ++ i)
scanf("%d", &x), a[i] = Abs(x), b[i] = Sgn(x);
build(1, 1, n);
for(int i = 1, o, l, r, x; i <= m; ++ i)
{
scanf("%d%d%d", &o, &l, &r);
if(o != 5)
scanf("%d", &x);
if(o == 1)
init(++ idx, Abs(x)), updCov(1, l, r, idx, Sgn(x));
if(o == 2)
updSgn(1, l, r, Sgn(x));
if(o == 3 && x < 0)
updRev(1, l, r);
if(o == 4)
init(++ idx, Abs(x)), updVal(1, l, r, idx, Sgn(x));
if(o == 5)
printf("%u\n", get(1, l, r).res[1]);
}
return 0;
}
9.24
A
题目描述
你需要找到 \(l,r\),使得 \(l\le r\le l+d\)。构造序列 \(b_1,b_2,\cdots,b_n\),其中
你要求出 \(\sum_{i=1}^{n−1} \lvert b_i−b_{i+1}\rvert\) 的最大值。
记得读清题,是 \(r \le l + d\) 不是 \(r \le l + d - 1\)。
猜凸性,三分,然后过了。但是不知道真假。
对于每个 \(i\),考虑 \(a_i,a_{i + 1}\) 对哪些 \(l\) 造成贡献。发现是一个分段函数,记录一下扫描线即可。
*C
题目描述
有一张 \(2\times n\) 的网格图,你要从左上角 \((1,1)\) 走到右下角 \((2,n)\)。每条边有边权,并且有额外的 $ m$ 条限制。每条限制形如:给定 \(i,j,c\),如果你同时走了 \((1,i)\) 到 \((1,i+1)\) 和 \((2,j)\) 到 \((2,j+1)\) 这两条边,那么你就需要额外 \(c\) 的代价。求走过的边权和加上代价的最小值。
\(1\le n \le 500\),\(1 \le m \le 1000\)。
看数据范围,猜网络流,想最小割,切糕模型。
源点为 \(s\),汇点为 \(t\)。
\(s \to i\) 流量 \(+ \infty\),不可割。\(i \to i + n\) 流量 \((1,i)\) 和 \((1,i + 1)\) 之间的边权。\(i + n \to t\) 流量 \((2,i)\) 和 \((2,i + 1)\) 之间的边权。割掉哪个就相当于走那条边。
\(i + n\) 和 \(i + n + 1\) 之间建双向边,流量为 \((1, i + 1)\) 和 \((2, i + 1)\) 的边权。如果 \(i\) 格子到 \(i + 1\) 格子选择走第一行,\(i + 1\) 到 \(i + 2\) 选择第二行,网络流上需要割掉 \(i + n + 1 \to i + n\) 之间的边,否则还可以通过 \(s \to i + 1 \to i + n + 1 \to i + n \to t\) 从源点流向汇点。反过来同理,所以是双向边。
\(i, j, c\) 的限制连边 \(i + n, j + n, c\),原理同上。
*D
题目描述
给定 \(n,m\),对所有长度为 \(n\),值域为 \([1,m]\) 的序列求众数出现次数之和。对 \(10^9+7\) 取模。多组数据。
\(1\le T \le 10\),\(1 \le n \le 1000\),\(1 \le m \le 10^9\)。
枚举 \(k\),计算所有数出现次数均 \(\le k\) 的方案数,然后统计答案。
考虑 \(\text{EGF}\),设 \(e_k(x) = \sum\limits_{i=0}^k \frac {x ^ i} {i !}\),答案为 \(n![x^n]e_k^m(x)\)。
如果不理解可以考虑选出一个值域为 \([1,m]\) 的可重集。设 \(o_{k}=\sum\limits_{i=0}^k x^i\),答案为 \([x^n]o_k^m(x)\)。
设 \(f_{i, j}\) 表示 \([x^j]e_k^i\)。
\(m\) 很大但是 \(n\) 很小。复杂度为 \(\sum_k O(\frac{n^2} k)=O(n^2 \log n)\)。
代码
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 1005;
const int mod = 1e9 + 7;
int T, n, m, ans, fac[N], inv[N], ifa[N], f[2][N], g[N];
int Pow(int x, int y)
{
int r = 1;
for(; y; y >>= 1, x = x * x % mod)
if(y & 1)
r = r * x % mod;
return r;
}
int calc(int k)
{
for(int i = 0; i <= n; ++ i)
f[0][i] = f[1][0] = 0;
int t = n / k + (n % k == 0 ? 0 : 1);
int l = max(0ll, m - t);
f[l & 1][0] = 1;
for(int i = l + 1; i <= m; ++ i)
{
f[i & 1][0] = 1;
for(int j = 1; j <= n - (m - i) * k; ++ j)
{
if(j - k - 1 >= 0)
f[i & 1][j] = (i * f[i & 1][j - 1] - i * ifa[k] % mod * f[i - 1 & 1][j - k - 1]) % mod * inv[j] % mod;
else
f[i & 1][j] = i * inv[j] % mod * f[i & 1][j - 1] % mod;
}
}
return f[m & 1][n];
}
void work()
{
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; ++ i)
g[i] = calc(i) * fac[n] % mod;
// for(int i = 1; i <= n; ++ i)
// printf("%lld ", g[i]);
// printf("\n");
ans = 0;
for(int i = 1; i <= n; ++ i)
ans = (ans + (g[i] - g[i - 1]) * i) % mod;
printf("%lld\n", (ans + mod) % mod);
}
signed main()
{
freopen("capital.in", "r", stdin);
freopen("capital.out", "w", stdout);
fac[0] = inv[0] = inv[1] = ifa[0] = ifa[1] = 1;
for(int i = 1; i <= 1000; ++ i)
fac[i] = fac[i - 1] * i % mod;
for(int i = 2; i <= 1000; ++ i)
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
for(int i = 2; i <= 1000; ++ i)
ifa[i] = ifa[i - 1] * inv[i] % mod;
scanf("%lld", &T);
while(T --)
work();
return 0;
}
9.25
A
题目描述
给定模式串 \(x,y\),给定串 \(s\),求是否可以把 \(s\) 划分成两个子序列,使得其中一个是 \(x\) 重复若干次(可能为空),另外一个是 \(y\) 重复若干次(可能为空)。多组数据。
保证 \(\sum\lvert s\rvert\le 5\times 10^5,\lvert x\rvert ,\lvert y\rvert \le50,T\le 10^4\)。
直接 dp。可以 bitset 优化。
*B
题目描述
给定一棵树,小 q 想把所有树边都删掉。初始他会选择一个点开始,每次可以执行以下三种操作之一:
- 选择一条相邻的未被删除的边,经过这条边并将其删除。
- 花费 \(A\) 的代价,选择一条被删除的边将其恢复。
- 花费 \(B\) 的代价,瞬移至某个点。
求最少花费多少代价能够删除所有树边。
选择一些边加重边,设有 \(K\) 个奇度数的点,那么需要进行 \(B\) 操作的次数为 \(\frac K 2 - 1\)。\(K\) 一定为偶数。
设 \(f_{u,0/1/2}\) 表示 \(u\) 子树加完重边后,\(u\) 子树没有奇点,或 \(u\) 点是奇点,或 \(u\) 点不是奇点的最小的代价。转移时比较进行 \(B\) 操作的次数和之前两个子树转移操作次数的和,有没有加一减一的变化。
代码
#include <cstdio>
#include <vector>
#include <algorithm>
#define int long long
using namespace std;
const int N = 500005;
int n, A, B, dp[N][3];
vector <int> e[N];
void dfs(int u, int fa)
{
dp[u][0] = 0;
dp[u][1] = dp[u][2] = 1e18;
for(int v : e[u])
{
if(v == fa)
continue;
dfs(v, u);
int t[3] = {dp[u][0], dp[u][1], dp[u][2]};
dp[u][0] = dp[u][1] = dp[u][2] = 1e18;
dp[u][1] = min(dp[u][1], t[0] + dp[v][0]);
dp[u][0] = min(dp[u][0], t[0] + dp[v][0] + A);
dp[u][1] = min(dp[u][1], t[0] + dp[v][1]);
dp[u][2] = min(dp[u][2], t[0] + dp[v][1] + A);
dp[u][1] = min(dp[u][1], t[0] + dp[v][2] + B);
dp[u][2] = min(dp[u][2], t[0] + dp[v][2] + A);
dp[u][2] = min(dp[u][2], t[1] + dp[v][0]);
dp[u][1] = min(dp[u][1], t[1] + dp[v][0] + A);
dp[u][2] = min(dp[u][2], t[1] + dp[v][1]);
dp[u][1] = min(dp[u][1], t[1] + dp[v][1] + A + B);
dp[u][2] = min(dp[u][2], t[1] + dp[v][2] + B);
dp[u][1] = min(dp[u][1], t[1] + dp[v][2] + A + B);
dp[u][1] = min(dp[u][1], t[2] + dp[v][0] + B);
dp[u][2] = min(dp[u][2], t[2] + dp[v][0] + A);
dp[u][1] = min(dp[u][1], t[2] + dp[v][1] + B);
dp[u][2] = min(dp[u][2], t[2] + dp[v][1] + A + B);
dp[u][1] = min(dp[u][1], t[2] + dp[v][2] + B + B);
dp[u][2] = min(dp[u][2], t[2] + dp[v][2] + A + B);
}
}
signed main()
{
freopen("analyse.in", "r", stdin);
freopen("analyse.out", "w", stdout);
scanf("%lld%lld%lld", &n, &A, &B);
for(int i = 1, u, v; i < n; ++ i)
scanf("%lld%lld", &u, &v), e[u].push_back(v), e[v].push_back(u);
dfs(1, 0);
printf("%lld\n", min({dp[1][0], dp[1][1], dp[1][2]}));
return 0;
}
**C
题目描述
用以下经典的方式构造一棵树:对 \(i=2\sim n\),从 \(1\sim i-1\) 中随机选择一个点当作 \(i\) 的父亲。
设点 \(u\) 的子树大小的 \(k\) 次方为 \(f_u\),给定 \(a_u\),求 \(\sum_u a_uf_u\) 的期望。对 \(10^9+7\) 取模。
把 \(u\) 子树大小 \(k\) 次方的期望改为:在 \(u\) 子树中选 \(k\) 个点,可放回的方案数。也就是说对于每个序列 \(u_1,\cdots,u_k\) 在 \(u\) 子树的概率。
加上 \(n + 1,n + 2, \cdots, n + k\) 这 \(k\) 个点,这些点的父亲在 \([1,n]\) 中随机。父节点为我们选的 \(k\) 个点。
设 \(f_{i,j}\) 表示考虑父节点在 \([i,n + k]\) 中的所有点,没定父亲的点在 \([1,i - 1]\) 中,当前 \(n + 1, \cdots,n + k\) 合并到了 \(j\) 个子树中的概率。初始时 \(f_{n + 1, k} = 1\)。
计算到 \(i\) 时,枚举这 \(j\) 个子树有多少个子树根的父节点为 \(i\),设为 \(t\)。此时 \(t \neq 0\)。转移系数是
\(i\) 子树大小的 \(k\) 次方就是 \(f_{i, 1} \times n^k\)。乘 \(n^k\) 是因为 \(f\) 数组计算的是选到这 \(k\) 个点,且这 \(k\) 个点在 \(i\) 子树内的概率。而我们求的是随机 \(k\) 个点在 \(i\) 子树的概率。然后再转移有 \(0\) 个子树的父节点为 \(i\) 的情况。
代码
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 100025;
const int mod = 1e9 + 7;
int n, m, ans, a[N], fac[N], inv[N], ifa[N], dp[2][25];
int Pow(int x, int y)
{
int r = 1;
for(; y; y >>= 1, x = x * x % mod)
if(y & 1)
r = r * x % mod;
return r;
}
int C(int n, int m)
{
return fac[n] * ifa[m] % mod * ifa[n - m] % mod;
}
signed main()
{
freopen("algebra.in", "r", stdin);
freopen("algebra.out", "w", stdout);
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; ++ i)
scanf("%lld", &a[i]);
fac[0] = inv[0] = inv[1] = ifa[0] = ifa[1] = 1;
for(int i = 1; i <= n + m; ++ i)
fac[i] = fac[i - 1] * i % mod;
for(int i = 2; i <= n + m; ++ i)
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
for(int i = 2; i <= n + m; ++ i)
ifa[i] = ifa[i - 1] * inv[i] % mod;
dp[n + 1 & 1][m] = 1;
for(int i = n + 1; i >= 2; -- i)
{
for(int j = 1; j <= m; ++ j)
for(int k = 1; k <= j; ++ k)
(dp[i - 1 & 1][j - k + 1] += C(j, k) * Pow(i - 2, j - k) % mod * Pow(inv[i - 1], j) % mod * dp[i & 1][j] % mod) %= mod;
ans = (ans + dp[i - 1 & 1][1] * a[i - 1] % mod) % mod;
for(int j = 1; j <= m; ++ j)
(dp[i - 1 & 1][j] += dp[i & 1][j] * Pow(i - 2, j) % mod * Pow(inv[i - 1], j) % mod) % mod;
for(int j = 0; j <= m; ++ j)
dp[i & 1][j] = 0;
}
printf("%lld\n", ans * Pow(n, m) % mod);
return 0;
}
放个暴力
对于每个 \(u\),设 \(f_{i,j}\) 表示考虑前 \(i\) 个点,子树大小为 \(j + 1\) 的方案数。
转移代码如下(from 5k_sync_closer):
f[i][0] = fac[i - 1];
for(int j = i + 1; j <= n; ++ j)
{
f[j][0] = f[j - 1][0] * (j - 2) % mod;
for(int o = 1; o <= n; ++ o)
f[j][o] = (f[j - 1][o] * (j - o - 2) + f[j - 1][o - 1] * o) % mod;
}
jijidawang 和 oceans_of_stars 的做法比标程好啊!
考虑 \(x\) 点,子树大小为 \(i\) 的方案数。
先选出 \(i - 1\) 个点在 \(x\) 子树内,方案数 \(n - x \choose i - 1\)。
\(x\) 子树内的方案数,\((i - 1)!\)。
\(x\) 子树外的方案数,\((n - i - 1)!\)
\(x\) 点的方案数,\(x - 1\)。
总答案为
所求的为期望,\(x\) 点的子树大小的 \(k\) 次方的期望为
分母是好求的,考虑分子。
上指标范德蒙德卷积
有 \(n+1\) 个球,选 \(a+b+1\) 个球。枚举第 \(a+1\) 个球的位置。
D
题目描述
有两个隐藏的数字 \(a,b\)。保证 \(1\le a,b\le N\)。你需要构造一系列询问,每次询问包含一个数字集合,你可以知道 \(a,b\) 中是否存在至少一个数在集合。你需要确保你可以通过这些询问找到这个无序对 \((a,b)\)。
询问次数 \(R \le 26\) 为满分。
构造 \(1000\) 个 \(26\) 位二进制数,满足不同的 \((a,b)\) 的 \(x_a \mid x_b\) 不同。
随机选择 \(a_i\) 判断能否加入可以跑出 \(R \ge 29\) 的解。
让 \(a_i\) 每一个 bit 有 \(p\) 的概率是 \(1\),取 \(\frac 1 3\sim \frac 1 4\),可以得出 \(R = 28\)。
选择 \(\text{popcount} = 8\) 的数随机,可得到 \(R = 27\)。
从小到大加入,如果一个数与集合中的数按位与的 \(\text{popcount} \ge 6\),就不加入。
然后把一些数随机加入,删一些数,加一些数,就能得到满分。
原题的一些子任务还有一些其他问题。
9.27
A
题目描述
给定 \(4\) 个数 \(a, b, c, d\)。找到 \(4\) 个非负整数 \(x,y,z,w\),满足以下条件:
使得 \(x + y + z + w\) 最小。输出最小值。
\(1 \le a,b,c,d \le 1500\)。
二分答案,枚举对角线的两个值,然后分情况讨论 \(\mod 4\) 的值。
说点其他的,比如一个二元一次不等式的解集是一个半平面。
还可以枚举 \(x+w,y+z\)。
B
题目描述
鸡尾酒很喜欢让别人爬,于是他种了一棵树。然后在树上放了 \(n\) 只蚂蚁,看它们爬。
树上有 \(n\) 个点,用 \(n − 1\) 条边相连,除了根结点外,每个结点都有父亲,根结点记为 \(1\) 号结点,其它结点分别编号为 \(2 \sim n\)。\(n\) 个点上分别放有一只蚂蚁,并且每只蚂蚁有一个属性 \(a_i\),除了根节点的蚂蚁之外,每只蚂蚁都可以选择向上爬到父亲结点或者不爬(只能爬一次)。在所有蚂蚁都选择完毕之后,我们统计这些蚂蚁产生的快乐值:如果多只蚂蚁在同一点,则产生这些蚂蚁属性的异或和的快乐值。如果某个点上只有一只蚂蚁或者没有蚂蚁,则不产生快乐值。问所有方案的所有快乐值之和。
树形 DP,按位考虑。先按照一只蚂蚁也有贡献做,再把一只蚂蚁的贡献减掉。比较复杂。
int_R 的做法很好啊。一个点的贡献只和它自己和儿子有关。设 \(s = \lvert son(u) \rvert + 1\),\(c_i\) 为 \(u\) 及其儿子中第 \(i\) 位为 \(1\) 的个数,则 \(u\) 点的贡献为:
有个式子
C
题目描述
你有 \(n\) 个字母 \(A\),\(m\) 个字母 \(B\),你可以将这些字母组成成为一个字符串,你需
要使得这个字符串的权值尽量大。现在我们以如下规则计算这个字符串的权值。
- 每有连续的 \(a\) 个 \(A\) ,且下一个字母依旧是 \(A\),则权值 \(+1\)。假设 \(a = 3\),且连续有 \(7\) 个 \(A\),那么根据此规则,权值 \(+2\)。你可以理解一段长度为 \(C\) 的 \(A\) 所获得的权值为 \(\lfloor \frac {C - 1} a\rfloor\)。
- 每有连续的 \(b\) 个 \(B\) ,且下一个字母依旧是 \(B\),则权值 \(+1\)。
- 上一个字母和当前字母不一样时,权值 \(+1\)。(第一个字母前面没有字母,也会使得权值 \(+1\),详见样例 1)
假设当前字母是 \(B\),则至少需要有连续 \(c\) 个字母 \(B\),下一个字母才可以切换成 \(A\)。字母 \(A\) 切换到字母 \(B\) 没有任何限制。
请问你能构造的字符串权值最大可能是多少?
设字符串 \(s\) 为 \(c\) 个 \(B\),一个 \(A\)。
枚举字符串 \(s\) 循环出现多少次,然后贪心把剩下的 \(A,B\) 字符用完。
\(A\) 字符先在最前面放一个,然后每有 \(a\) 个 \(A\) 可以增加 \(1\) 的贡献。
\(B\) 字符可以先在最后放一个,然后把中间的 \(B\) 字符补成 \(kb + 1\) 的长度,然后每有 \(b\) 个 \(B\) 可以增加 \(1\) 的贡献。
D
题目描述
鸡尾酒有一个奇怪的函数 \(F(x)\),这个函数的输入参数是一个正整数 \(x\),为了得到这个函数的运算结果,这个函数需要依次进行 \(n\) 个步骤,每个步骤是如下三种形式之一:
- \(x \leftarrow x + val\)
- \(x \leftarrow \min(x, val)\)
- \(x \leftarrow \max(x, val)\)
依次执行完这 \(n\) 个步骤之后,这个函数就可以安心输出答案了。
现在,鸡尾酒得到了这个函数,他想简化这个函数,确切的来说,他有 \(q\) 个问题,每个问题要么是修改这个函数的某一个步骤,要么给定一个 \(x\),询问当前 \(F(x)\) 的值,请帮助他完成这个过程。
发现是分段函数,形如:
证明考虑归纳。
线段树维护分段函数即可。
重要的不是这个,考虑构造 \((\min, +)\) 矩阵,\(C_{i,j} = \min_k(A_{i,k}+B_{k,j})\)。
当前数字 \(x\) 对 \(val\) 取 \(\min\)。
当前数字加 \(val\)。
如果只有 \(1,2\) 操作,可以直接动态 DP。
代码
#include <cstdio>
#include <algorithm>
#define ls p << 1
#define rs p << 1 | 1
using namespace std;
const int N = 100005;
const int inf = 1000000000;
int n, m, a[N], b[N];
struct Node
{
int l, r, o, x;
};
struct Tree
{
int l, r, c;
Node t[3];
};
Tree s[N << 2];
Node f[10];
void push_up(int p)
{
int k = -1;
for(int i = 0; i < s[ls].c; ++ i)
{
for(int j = 0; j < s[rs].c; ++ j)
{
if(s[ls].t[i].o == 1)
{
if(s[ls].t[i].x >= s[rs].t[j].l && s[ls].t[i].x <= s[rs].t[j].r)
{
if(s[rs].t[j].o == 1)
f[++ k] = {s[ls].t[i].l, s[ls].t[i].r, 1, s[rs].t[j].x};
else
f[++ k] = {s[ls].t[i].l, s[ls].t[i].r, 1, s[ls].t[i].x + s[rs].t[j].x};
}
}
else
{
int L = s[ls].t[i].l + s[ls].t[i].x, R = s[ls].t[i].r + s[ls].t[i].x;
if(L <= s[rs].t[j].r && R >= s[rs].t[j].l)
{
L = max(L, s[rs].t[j].l) - s[ls].t[i].x;
R = min(R, s[rs].t[j].r) - s[ls].t[i].x;
if(s[rs].t[j].o == 1)
f[++ k] = {L, R, 1, s[rs].t[j].x};
else
f[++ k] = {L, R, 2, s[ls].t[i].x + s[rs].t[j].x};
}
}
}
}
s[p].c = 0;
for(int i = 0; i <= k; ++ i)
{
if(i != 0 && f[i].o == f[i - 1].o && f[i].x == f[i - 1].x)
s[p].t[s[p].c - 1].r = f[i].r;
else
s[p].t[s[p].c ++] = f[i];
}
s[p].t[0].l = 1;
s[p].t[s[p].c - 1].r = inf;
}
void upd(int p, int l)
{
s[p].c = 0;
if(a[l] == 1)
s[p].t[0] = {1, inf, 2, b[l]}, s[p].c = 1;
if(a[l] == 2)
{
if(b[l] != 1)
s[p].t[s[p].c ++] = {1, b[l] - 1, 2, 0};
s[p].t[s[p].c ++] = {b[l], inf, 1, b[l]};
}
if(a[l] == 3)
{
s[p].t[s[p].c ++] = {1, b[l], 1, b[l]};
s[p].t[s[p].c ++] = {b[l] + 1, inf, 2, 0};
}
}
void build(int p, int l, int r)
{
s[p].l = l;
s[p].r = r;
if(l == r)
return upd(p, l);
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
push_up(p);
}
void update(int p, int x)
{
if(s[p].l == s[p].r)
return upd(p, x);
int mid = s[p].l + s[p].r >> 1;
if(x <= mid)
update(ls, x);
else
update(rs, x);
push_up(p);
}
int main()
{
freopen("function.in", "r", stdin);
freopen("function.out", "w", stdout);
scanf("%d", &n);
for(int i = 1; i <= n; ++ i)
scanf("%d%d", &a[i], &b[i]);
build(1, 1, n);
scanf("%d", &m);
for(int i = 1, o, x, y; i <= m; ++ i)
{
scanf("%d%d", &o, &x);
if(o <= 3)
scanf("%d", &y), a[x] = o, b[x] = y, update(1, x);
else
{
for(int j = 0; j < s[1].c; ++ j)
{
if(x >= s[1].t[j].l && x <= s[1].t[j].r)
{
printf("%d\n", s[1].t[j].o == 1 ? s[1].t[j].x : (s[1].t[j].x + x));
break;
}
}
}
}
return 0;
}
杂
翻洛谷讨论区,看到一道题,第二天写的博客。
9.28
A
DP。
B
分情况讨论。删掉一个数是否对前面有影响。思考清楚。
*D
题目描述
现在给定 \(n,k,p\),请你求出 \(n\) 个点的无标号无根树中,直径长度为 \(k\) 且恰有 \(p\) 条直径的树有多少?
\(1 \le n,p \le 40\),\(1 \le k < n\)。
考虑转换成以直径中点为根的有根树个数。
设 \(f_{i,j,k}\) 表示子树节点数为 \(i\),深度为 \(k\) 的节点数为 \(j\),最深深度为 \(k\) 的有根树个数。称深度最深的叶子节点为有效叶子。
我们把节点数为 \(x\),深度为 \(z\) 的节点数为 \(y\),最深深度为 \(z\) 的有根树用三元组 \((x,y,z)\) 表示。
把 \(t\) 棵 \(v(x,y,z)\) 接到 \(u(i,j,k)\) 节点上,即令 \(t\) 棵状态为 \((x,y,z)\) 的树的父节点为 \(u\)。这个的方案数并不是 \(f_{i,j,k}\times f_{x,y,z}^t\)。因为状态为 \((x,y,z)\) 的树有很多种,相当于在 \(f_{x,y,z}\) 棵树中可重复选择 \(t\) 棵。答案为 \(f_{x,y,z} + t - 1 \choose t\)。
对于每一种 \((x,y,z)\),选择的数目不同,当做不同种物品。每一种 \((x,y,z)\) 的不同物品最多选 \(1\) 个。相当于分组背包。
这样就可以转移 \(f\) 了。
对于直径长度为奇数来说,直径中点是一条边。枚举两边子树大小和有效叶子数即可。
对于偶数,设 \(g_{i,j,k}\) 表示子树节点数为 \(i\),有效叶子数为 \(j\),直径数目为 \(k\) 的答案。转移类似上面。
代码
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 45;
const int mod = 998244353;
int n, len, num, ans, fac[N], inv[N], ifa[N], f[N][N][N], g[N][N][N];
int C(int n, int m)
{
int res = 1;
for(int i = n - m + 1; i <= n; ++ i)
res = i % mod * res % mod;
return res * ifa[m] % mod;
}
signed main()
{
freopen("dia.in", "r", stdin);
freopen("dia.out", "w", stdout);
scanf("%lld%lld%lld", &n, &len, &num);
fac[0] = inv[0] = inv[1] = ifa[0] = ifa[1] = 1;
for(int i = 1; i <= 40; ++ i)
fac[i] = fac[i - 1] * i % mod;
for(int i = 2; i <= 40; ++ i)
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
for(int i = 2; i <= 40; ++ i)
ifa[i] = ifa[i - 1] * inv[i] % mod;
f[1][1][0] = 1;
// f[i][j][k]: 子树大小为 i,有效叶子数为 j,深度为 k 的方案数
for(int a = 1; a <= n; ++ a)
{
for(int b = 1; b <= a; ++ b)
{
for(int c = 0; c <= len / 2; ++ c)
{
if(!f[a][b][c])
continue;
for(int i = n; i >= 1; -- i)
{
for(int j = 1; j <= i; ++ j)
{
for(int k = 0; k <= len / 2; ++ k)
{
if(!f[i][j][k])
continue;
for(int t = 1; i + a * t <= n && j + b * t <= n; ++ t)
{
int A = i + a * t, B, D = max(c + 1, k);
if(i == 1)
B = b * t;
else if(c + 1 == k)
B = j + b * t;
else if(c + 1 > k)
B = b * t;
else
B = j;
(f[A][B][D] += f[i][j][k] * C(f[a][b][c] + t - 1, t)) %= mod;
}
}
}
}
}
}
}
if(len & 1)
{
for(int a = 1; a <= n / 2; ++ a)
{
for(int b = 1; b <= a; ++ b)
{
if(!f[a][b][len / 2])
continue;
for(int c = 1; c <= n - a; ++ c)
{
if(b * c == num && (n - a != a || b <= c))
ans = (ans + f[a][b][len / 2] * f[n - a][c][len / 2]) % mod;
}
}
}
printf("%lld\n", ans);
return 0;
}
// g[i][j][k]: 子树大小为 i,有效叶子数 j,k 条直径的方案数
g[1][0][0] = 1;
for(int a = 1; a <= n; ++ a)
{
for(int b = 1; b <= a; ++ b)
{
for(int c = 0; c <= len / 2 - 1; ++ c)
{
if(!f[a][b][c])
continue;
for(int i = n; i >= 1; -- i)
{
for(int j = 0; j <= i; ++ j)
{
for(int k = 0; k <= num; ++ k)
{
if(!g[i][j][k])
continue;
for(int t = 1; i + a * t <= n && j + b * t <= n; ++ t)
{
int B = j + ((c == len / 2 - 1) ? b * t : 0);
int D = k + ((c == len / 2 - 1) ? t * b * j + t * (t - 1) / 2 * b * b : 0);
if(D <= num)
(g[i + a * t][B][D] += g[i][j][k] * C(f[a][b][c] + t - 1, t)) %= mod;
}
}
}
}
}
}
}
for(int j = 1; j <= n; ++ j)
ans = (ans + g[n][j][num]) % mod;
printf("%lld\n", (ans + mod) % mod);
return 0;
}