题解【洛谷P2221】[HAOI2012]高速公路
我们需要一棵支持区间加、区间询问所有子区间和的线段树。
对于询问,假设询问区间是 \([l,r]\),答案即为:
\[\dfrac{\sum\limits_{i=l}^r\sum\limits_{j=i}^r dis_{i,j}}{\binom{r-l+1}{2}}
\]
分母可以直接 \(\mathcal{O}(1)\) 算;
对于分子,我们发现点之间的距离不好用线段树维护,于是我们把边转成点,然后考虑计算每一条边对答案的贡献:
\[\begin{aligned}\sum_{i=l}^r v_i(r-i+1)(i-l+1) &= \sum\limits_{i=l}^r v_i(ri-lr+r-i^2+il-i+i-l+1) \\
&= \sum\limits_{i=l}^r -i^2v_i+iv_i(r+l)+v_i(-lr+r-l+1)\end{aligned}\]
令 \(s_1=\sum v_i\),\(s_2=\sum iv_i\),\(s_3=\sum i^2v_i\)。
答案即为:
\[\begin{aligned} -s3+s_2(r+l)+s_1(-lr+r-l+1)\end{aligned}
\]
可以开 \(3\) 棵线段树解决。
然后考虑怎么修改。
\(s_1\):直接区间修改即可。
\(s_2\):\(\sum i(v_i+k)=\sum iv_i+ik\),可以维护一个前缀和 \(s_4\) 记录 \(\sum i\)。
\(s_3\):\(\sum i^2(v_i+k)=\sum i^2v_i+i^2k\),维护一个前缀和 \(s_5\) 记录 \(\sum i^2\)。
推荐练习:[SDOI2017]相关分析。
代码:
#include <bits/stdc++.h>
#define DEBUG fprintf(stderr, "Passing [%s] line %d\n", __FUNCTION__, __LINE__)
#define File(x) freopen(x".in","r",stdin); freopen(x".out","w",stdout)
#define int long long
using namespace std;
typedef long long LL;
typedef pair <int, int> PII;
typedef pair <int, PII> PIII;
template <typename T>
inline T gi()
{
T f = 1, x = 0; char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return f * x;
}
const int INF = 0x3f3f3f3f, N = 100003, M = N << 1;
int n, m;
LL sum[4][N << 2], lz[4][N << 2];
LL pr[3][N << 2];
inline int ls(int p) {return p << 1;}
inline int rs(int p) {return p << 1 | 1;}
inline void pushup(int p)
{
for (int i = 1; i <= 3; i+=1) sum[i][p] = sum[i][ls(p)] + sum[i][rs(p)];
}
inline void mark(int l, int r, int val, int p)
{
sum[1][p] += 1ll * val * (r - l + 1),
sum[2][p] += 1ll * val * (pr[1][r] - pr[1][l - 1]),
sum[3][p] += 1ll * val * (pr[2][r] - pr[2][l - 1]);
lz[1][p] += val, lz[2][p] += val, lz[3][p] += val;
}
inline void pushdown(int l, int r, int p)
{
int mid = (l + r) >> 1;
if (lz[1][p])
{
sum[1][ls(p)] += 1ll * lz[1][p] * (mid - l + 1),
sum[1][rs(p)] += 1ll * lz[1][p] * (r - mid);
lz[1][ls(p)] += lz[1][p],
lz[1][rs(p)] += lz[1][p];
lz[1][p] = 0;
}
if (lz[2][p])
{
sum[2][ls(p)] += 1ll * lz[2][p] * (pr[1][mid] - pr[1][l - 1]),
sum[2][rs(p)] += 1ll * lz[2][p] * (pr[1][r] - pr[1][mid]);
lz[2][ls(p)] += lz[2][p],
lz[2][rs(p)] += lz[2][p];
lz[2][p] = 0;
}
if (lz[3][p])
{
sum[3][ls(p)] += 1ll * lz[3][p] * (pr[2][mid] - pr[2][l - 1]),
sum[3][rs(p)] += 1ll * lz[3][p] * (pr[2][r] - pr[2][mid]);
lz[3][ls(p)] += lz[3][p],
lz[3][rs(p)] += lz[3][p];
lz[3][p] = 0;
}
}
void modify(int ql, int qr, int val, int l, int r, int p)
{
if (ql <= l && r <= qr)
{
mark(l, r, val, p);
return;
}
pushdown(l, r, p);
int mid = (l + r) >> 1;
if (ql <= mid) modify(ql, qr, val, l, mid, ls(p));
if (qr > mid) modify(ql, qr, val, mid + 1, r, rs(p));
pushup(p);
}
LL getans(int id, int ql, int qr, int l, int r, int p)
{
if (ql <= l && r <= qr) return sum[id][p];
pushdown(l, r, p);
int mid = (l + r) >> 1;
LL sum = 0;
if (ql <= mid) sum += getans(id, ql, qr, l, mid, ls(p));
if (qr > mid) sum += getans(id, ql, qr, mid + 1, r, rs(p));
return sum;
}
inline LL query(int l, int r)
{
LL s1 = getans(1, l, r, 1, n - 1, 1), s2 = getans(2, l, r, 1, n - 1, 1), s3 = getans(3, l, r, 1, n - 1, 1);
return s2 * (l + r) + s1 * (r + 1 - l - l * r) - s3;
}
signed main()
{
//File("");
n = gi <int> (), m = gi <int> ();
for (int i = 1; i <= n; i+=1)
pr[1][i] = pr[1][i - 1] + i, pr[2][i] = pr[2][i - 1] + 1ll * i * i;
while (m--)
{
char op[3];
scanf("%s", op);
int l = gi <int> (), r = gi <int> () - 1, v;
if (op[0] == 'C')
{
v = gi <int> ();
modify(l, r, v, 1, n - 1, 1);
}
else
{
if (l > r) {puts("0/1"); continue;}
LL len = 1ll * (r - l + 1) * (r - l + 2) / 2;
LL ans = query(l, r);
LL G = __gcd(ans, len);
ans /= G, len /= G;
printf("%lld/%lld\n", ans, len);
}
}
return 0;
}