树状数组维护差分序列
差分的性质
bi 为原序列 ai 的差分序列,则
bi={ai−ai−1a1i∈[2,n]i=1
Acwing242
根据差分序列的性质,我们用树状数组维护差分序列
- 初始化树状数组,tr[i]=A[i]−A[i−1]
add(i,Ai−Ai−1)
- 区间 [l,r]+d (区间内所有的数都加 d)
add(l,d),add(r+1,−d)
- 询问修改后下标为 x 的值,执行
sum(x)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| const int maxn = 100000 + 10; ll a[maxn], tr[maxn]; int n, m;
void add(int x, int d) { for (int i = x; i <= n; i += lowbit(i)) tr[i] += d; }
ll sum(int x) { ll ans = 0; for (int i = x; i; i -= lowbit(i)) ans += tr[i]; return ans; }
int main() { freopen("input.txt", "r", stdin); scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
// prework for (int i = 1; i <= n; i++) add(i, a[i]-a[i-1]);
// solve while (m--) { char cmd[2]; scanf("%s", cmd); if (cmd[0] == 'Q') { int x; scanf("%d", &x); printf("%lld\n", sum(x)); } else { int l, r, d; scanf("%d%d%d", &l, &r, &d); add(l, d), add(r+1, -d); } } }
|
差分序列维护原序列部分和
ax 的前缀和 S=∑i=1xai
如果用差分数组来维护
S=i=1∑xj=1∑ibj=i=1∑x(x−i+1)⋅bi
也就是说
S(x)=(x+1)i=1∑xbi−i=1∑xi⋅bi
- 用两个树状数组 tr1,tr2 分别维护序列
{bi},{ibi}
- [l,r]+d,转为单点增加
add(tr1,l,d), add(tr1,r+1,−d)
add(tr2,l,l⋅d), add(tr2,r+1,−(r+1)⋅d)
- [l,r] 部分和,根据表达式 S(r)−S(l−1)
S(r)=(r+1)⋅sum(tr1,r)−sum(tr2,r)
Acwing243
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| const int maxn = 100000 + 10; ll tr1[maxn], tr2[maxn], a[maxn]; int n, m;
void add(ll tr[], int x, ll d) { for (int i = x; i <= n; i += lowbit(i)) tr[i] += d; }
ll sum(const ll tr[], int x) { ll res = 0; for (int i = x; i; i -= lowbit(i)) res += tr[i]; return res; }
ll S(int x) { return (x+1) * sum(tr1, x) - sum(tr2, x); }
int main() { freopen("input.txt", "r", stdin); scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
// prework for (int i = 1; i <= n; i++) { add(tr1, i, a[i]-a[i-1]); add(tr2, i, i * (a[i]-a[i-1])); }
// solve while (m--) { char cmd[2]; scanf("%s", cmd); if (cmd[0] == 'Q') { int l, r; scanf("%d%d", &l, &r); ll res = S(r) - S(l-1); printf("%lld\n", res); } else { int l, r, d; scanf("%d%d%d", &l, &r, &d); add(tr1, l, d), add(tr1, r+1, -d); add(tr2, l, (ll)l*d), add(tr2, r+1, -(ll)(r+1)*d); } } }
|
线段树维护差分序列
区间最大公约数问题
gcd(x1,x2)=gcd(x1,x2−x1)gcd(xl,xl+1,xl+2⋯xn)=gcd(xl,xl+1−xl,xl+2−xl+1⋯)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| const int maxn = 500000 + 10; ll a[maxn], b[maxn]; int n, m;
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }
ll tr[maxn]; void add(int x, ll d) { for (int i = x; i <= n; i += lowbit(i)) tr[i] += d; } ll sum(int x) { ll res = 0; for (int i = x; i; i -= lowbit(i)) res += tr[i]; return res; }
class A { public: int l, r; ll val; } t[maxn<<2];
void build(int p, int l, int r) { t[p].l = l; t[p].r = r; if (l == r) { t[p].val = b[l]; return; } int mid = (l+r) >> 1; if (l <= mid) build(p<<1, l, mid); if (r > mid) build(p<<1 | 1, mid + 1, r);
t[p].val = gcd(t[p<<1].val, t[p<<1 | 1].val); }
void change(int p, int x, ll d) { if (x == t[p].l && t[p].r == x) { t[p].val += d; return; } int mid = (t[p].l + t[p].r) >> 1; if (x <= mid) change(p<<1, x, d); if (x > mid) change(p<<1 | 1, x, d); t[p].val = gcd(t[p<<1].val, t[p<<1 | 1].val); }
ll query(int p, const int l, const int r) { if (l > r) return 0; if (l <= t[p].l && t[p].r <= r) return t[p].val;
int mid = (t[p].l + t[p].r) >> 1; ll res = 0; if (l <= mid) res = gcd(res, query(p<<1, l, r)); if (r > mid) res = gcd(res, query(p<<1 | 1, l, r)); return res; }
void prework() { for (int i = 1; i <= n; i++) { add(i, b[i]); } build(1, 1, n+1); }
int main() { freopen("input.txt", "r", stdin); scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%lld", &a[i]); for (int i = 1; i <= n; i++) b[i] = a[i] - a[i-1];
// use tr[...] and seg-tree prework();
// solve command while (m--) { char cmd[2]; scanf("%s", cmd); if (cmd[0] == 'Q') { int l, r; scanf("%d%d", &l, &r); ll res = query(1, l+1, r); ll al = sum(l); res = gcd(al, res); printf("%lld\n", abs(res)); } else { int l, r; ll d; scanf("%d%d%lld", &l, &r, &d); change(1, l, d); change(1, r+1, -d); add(l, d); add(r+1, -d); } } }
|
差分 dp
dp 中的背包模型
背包问题中容易混淆的是 0-1背包和完全背包
0-1背包和完全背包,dp阶段的更新方式分别如左右两图所示
差分算法解决 dp 问题
Problem Scores
问题简述
序列中有 n 个元素,满足 A1⩽A2⩽⋯⩽An
从中选取任意 k 个元素,他们的和为 ∑ka[⋯]
从中选取任意 k+1 个元素,他们的和为 ∑k+1a[⋯]
必须保证 ∑ka[⋯]<∑k+1a[⋯],求满足条件的方案数
注意到此时 A1⩽A2⩽⋯⩽An
所以原问题可以转换成为
i=1∑k+1ai⩾i=n−k+1∑nai
先证明一个性质,如果 n−k+1=k+1,即 k=⌊2n⌋ 满足
则对所有的 1⩽k⩽n−1 均满足
证明如下
问题转换为满足
i=1∑k+1ai⩾i=n−k+1∑nai(k=⌊2n⌋)
-
构造差分序列 xi=ai−ai−1
令 x0=1,这是因为得分不能为 0,可能所有题目得分都是一样的
此时 ∑i=1nxi=0 不满足题意
所以令 x0=1,则此时 an⩾1 成立
an=1+(i=1∑nxi)
-
⌊2n⌋=2n−1
i=1∑2n+1ai⩾i=2n+1+1∑nai⇒ 2⎝⎛i=1∑2n+1ai⎠⎞⩾i=1∑nai⇒2i=1∑2n+1(2n+1−i+1)xi⩾i=1∑n(n−i+1)xi
移项,上式可以写成
∑i=1nCi⋅xi⩽0
Ci={n−i+1i−2i>2n+1i⩽2n+1
-
⌊2n⌋=2n
同理可以求出
2⎝⎛i=1∑2nai⎠⎞⩾(i=1∑nai)−a2n+12⎝⎛i=1∑2n(2n−i+1)xi⎠⎞⩾i=1∑n(n−i+1)xi−i=1∑2n+1xi
写成如下形式
∑i=1nCi⋅xi⩽0
Ci=⎩⎪⎪⎨⎪⎪⎧n−in−i+1i−2i=2n+1i>2n+1i⩽2n
注意到 i=2n+1 时候,此时 i−2=n−i
综上所述
Ci={n−i+1i−2i>⌊2n⌋+1i⩽⌊2n⌋+1
-
问题转换为满足以下条件
{∑i=1nCixi⩽01+(∑i=1nxi)⩽n
合法的 xi 取值方案数(注意此时 xi 未知),其中
Ci={n−i+1i−2i>⌊2n⌋+1i⩽⌊2n⌋+1
这是一个线性约束条件,对 i 归纳,只要 x1 满足约束
可以递推出其余的 x2⋯xn 方案
化简得到
{x1⩽(n−1)−(∑i=2nxi)x1⩾∑i=2nCixi
将不等式看作关于 x1 取值区间的两个端点,x1 能取的值一共有
cnt=max(0,n−i=2∑n(Ci+1)xi)
-
这个问题可以用 O(n2) 的 dp 解决
假设满足 ∑i=2n(Ci+1)xi=P,此时对于{xi} 序列,合法的方案数有 f(P) 个
(注意此时 xi 未知)
则最后的答案是
(n−P)⋅f(P),P=∑Vixi(Vi=Ci+1)
- f(P) 可以通过完全背包的刷表法解决
f(P)=∑i=2n(Ci+1)xi,此时背包的物品种类 i∈[2,n]
每一种物品的个数 xi 未知,但每一种物品可以重复选
第 i 种物品的体积为 Vi=Ci+1
∑Vixi=P 其实相当于背包的容积为 P,P∈[0⋯n],对于第 i 阶段递推式
P∈[Vi⋯n]
- f(i,P)←f(i,P)+f(i,P−Vi),P∈[Vi→n]
f(0)=1,此时我们有 ∀i, xi=0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| const int maxn = 5000 + 10; int n, m, C[maxn]; ll f[maxn];
void prework() { for (int i = 1; i <= n; i++) { int mid = n / 2; if (i > mid + 1) C[i] = n-i+2; else C[i] = i-1; } }
void dp() { memset(f, 0, sizeof f); f[0] = 1; for (int i = 2; i <= n; i++) { for (int j = C[i]; j <= n; j++) { f[j] = (f[j] + f[j-C[i]]) % m; } }
ll ans = 0; for (int i = 0; i <= n; i++) { ans = (ans + 1ll * (n-i) * f[i]) % m;
} printf("%lld\n", ans); }
int main() { freopen("input.txt", "r", stdin); cin >> n >> m;
// prework prework(); // dp dp(); }
|