#线段树
题目链接
第一次写线段树……接近100行的代码(加注释)有点吃不消啊
以后应该会好很多吧?
主要注意一下懒标记得问题。
其他除了有点绕,勉强还行。
#include
using namespace std;
const int N = 4e5 + 10;
#define ll long long
int a[N];
ll n, m;
//个人习惯:将这些与树状数组的元素直接挂钩的写在结构体里面
struct T {
//区间范围,区间中心
ll l, r, mid;
//对应属性
ll sum;
//懒标记
ll plz;
//左右子节点位置:left son position,right son position
ll lsp, rsp;
}t[N];
void build(ll i, ll l, ll r){
t[i].l = l;
t[i].r = r;
t[i].mid = (l + r) >> 1;
t[i].lsp = i << 1;
t[i].rsp = i << 1 | 1;
//当前节点是叶子节点(赋值为a[i],结束)
if(l == r) {
t[i].sum = a[l];
return ;
}
//不是叶子节点进行以下操作:
//不断往下面二分原数组
build(t[i].lsp, l, t[i].mid);
build(t[i].rsp, t[i].mid + 1, r);
//求和:两子节点.sum相加
t[i].sum = (t[t[i].lsp].sum + t[t[i].rsp].sum);
return ;
}
void addplz(ll i, ll plz) {
//第一个操作:把父节点的懒标记吃了
t[i].sum += (t[i].r - t[i].l + 1) * plz;
//回归定义:懒标记是留给子节点吃的
//所以要加上来自父节点的懒标记
t[i].plz += plz;
}
void pushdown(ll i) {
if(t[i].plz) {
//有两个同样的操作,所以用子函数
addplz(t[i].lsp, t[i].plz);
addplz(t[i].rsp, t[i].plz);
//子节点已经吃了,懒标记作用完成,归零
t[i].plz = 0;
}
return ;
}
void add(ll i, ll l, ll r, ll k) {
//分四种情况:
//第一种:线段树区间[ti.l, ti.r]完全不包括目标段
if(t[i].l > r || t[i].r < l) return ;
//第二种:线段树区间[ti.l, ti.r]完全包括目标段
//懒标记:留给子节点吃.因为:遇到一个新的k,父节点先吃,精确到子节点时,子节点再吃,可以类推.
if(t[i].l >= l && t[i].r <= r) {
//父节点先吃.(这里的父节点,指代当前区间)
t[i].sum += ( (t[i].r - t[i].l + 1) * k);
//懒标记:留给子节点吃.因为:遇到一个新的k,父节点先吃,精确到子节点时,子节点再吃,可以类推.
t[i].plz = (t[i].plz + k);
return ;
}
//不是以上情况,说明与子节点有交集,就要调用到子节点了,此时就要将父节点的懒标记下放,确保子节点的值是正确的.
//注意:这里下放的懒标记不包含k
pushdown(i);
if(t[t[i].lsp].r >= l) add(t[i].lsp, l, r, k);
if(t[t[i].rsp].l <= r) add(t[i].rsp, l, r, k);
//回溯后,加上两子节点的值
t[i].sum = (t[t[i].lsp].sum + t[t[i].rsp].sum);
return ;
}
ll search(ll i,ll l,ll r){
//这里也是上面四种情况
//一、完全不包括
if(t[i].r < l || t[i].l > r) return 0;
//二、完全包括
if(t[i].l >= l && t[i].r <= r) return t[i].sum;
//精确到子区间找:
//找之前下放懒标记
pushdown(i);
//另外定义一个sum,不要修改.sum
ll sum = 0;
//目标区间与左节点有交集
if(t[t[i].lsp].r >= l) sum += search(t[i].lsp, l, r);
//目标区间与右节点有交集
if(t[t[i].rsp].l <= r) sum += search(t[i].rsp, l, r);
return sum;
}
int main() {
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> a[i];
build(1, 1 ,n);
while(m --)
{
int k; cin >> k;
if(k == 1) {
ll x, y, c;
scanf("%lld%lld%lld", &x, &y, &c);
add(1, x, y, c);
}
else {
ll x, y;
scanf("%lld%lld", &x, &y);
printf("%lld\n", search(1, x, y));
}
}
return 0;
}
#include
using namespace std;
const int N = 4e6 + 10;
#define ll long long
ll a[N];
ll MOD;
ll n, m;
//我怕麻烦就把所有东西弄在结构体里面了
struct T {
ll l, r, mid;
ll sum;
ll plz, mlz;
ll lsp, rsp;
}t[N];
void build(ll i, ll l, ll r){
t[i].l = l;
t[i].r = r;
t[i].mid = (l + r) >> 1;
t[i].lsp = i << 1;
t[i].rsp = i << 1 | 1;
t[i].mlz = 1;
//这是叶子节点(把对应a[l]赋值,然后结束)
if(l == r) {
t[i].sum = a[l] % MOD;
return ;
}
//不是叶子的会进行以下操作:
//不断二分往下建树
build(t[i].lsp, l, t[i].mid);
build(t[i].rsp, t[i].mid + 1, r);
//求和:两子节点的和相加。
t[i].sum = (t[t[i].lsp].sum + t[t[i].rsp].sum) % MOD;
return ;
}
void addplz(ll i, ll plz, ll mlz) {
//第1个操作:把懒标记吃了
t[i].sum = (t[i].sum * mlz + (t[i].r - t[i].l + 1) * plz) % MOD;
//吃了还要给下一个子节点留着(想想懒标记定义: 给子节点留着的)
//所以第2个操作:把父节点给的懒标记 与 自己原有的合起来
t[i].plz = (t[i].plz * mlz + plz) % MOD;
t[i].mlz = (t[i].mlz * mlz) % MOD;
}
void pushdown(ll i) {
//因为下放会有两个操作,所以用子函数.
addplz(t[i].lsp, t[i].plz, t[i].mlz);
addplz(t[i].rsp, t[i].plz, t[i].mlz);
//归零\1
t[i].plz = 0;
t[i].mlz = 1;
return ;
}
void add_p(ll i, ll l, ll r, ll k) {
//分 四种情况:
//第一:线段树区间[ti.l, ti.r] 完全不包含目标段:
if(t[i].l > r || t[i].r < l) return ;
//第一:线段树区间[ti.l, ti.r] 完全 包含目标段:使出 懒标记 大法
//懒标记是指:走到当前节点,才把当前节点要加上的数加上,子节点(区间每一个元素)要加的数,先存在父节点的plz
if(t[i].l >= l && t[i].r <= r) {
//这里用到了这整个区间,现在就把plz吃了
t[i].sum = (t[i].sum + (t[i].r - t[i].l + 1) * k) % MOD;
t[i].plz = (t[i].plz + k) % MOD;
return ;
}
//如果都不是,就要下放plz给子节点,否则子节点会遗漏应加上的数
//注意:这里给子节点吃的懒标记不包括k,是之前的
pushdown(i);
if(t[t[i].lsp].r >= l) add_p(t[i].lsp, l, r, k);
if(t[t[i].rsp].l <= r) add_p(t[i].rsp, l, r, k);
//回溯之后,加上新的子节点的值
t[i].sum = (t[t[i].lsp].sum + t[t[i].rsp].sum) % MOD;
return ;
}
//乘法
void add_m(ll i, ll l, ll r, ll k) {
if(t[i].l > r || t[i].r < l) return ;
if(t[i].l >= l && t[i].r <= r) {
t[i].sum = (t[i].sum * k) % MOD;
t[i].mlz = (t[i].mlz * k) % MOD;
t[i].plz = (t[i].plz * k) % MOD;
return ;
}
pushdown(i);
if(t[t[i].lsp].r >= l) add_m(t[i].lsp, l, r, k);
if(t[t[i].rsp].l <= r) add_m(t[i].rsp, l, r, k);
t[i].sum = (t[t[i].lsp].sum + t[t[i].rsp].sum) % MOD;
return ;
}
ll search(ll i,ll l,ll r){
//这里与上面差不多,都是四种情况
//第一是完全不包括
if(t[i].r < l || t[i].l > r) return 0;
//第二是完全包括
if(t[i].l >= l && t[i].r <= r) return t[i].sum % MOD;
//然后是还要在子区间里面找
//找之前先把懒标记下放
pushdown(i);
//定义另一个sum ,不要修改原来的.sum
ll sum = 0;
//目标区间与左子树有交集
if(t[t[i].lsp].r >= l) sum += search(t[i].lsp, l, r);
//目标区间与右子树有交集
if(t[t[i].rsp].l <= r) sum += search(t[i].rsp, l, r);
return sum % MOD;
}
int main() {
// freopen("ttt.in", "r", stdin);
// freopen("ttt.out", "w", stdout);
cin >> n >> m >> MOD;
for(int i = 1; i <= n; i ++) {
cin >> a[i];
a[i] %= MOD;
}
build(1, 1 ,n);
for(int i = 1; i <= m; i ++) {
int k; cin >> k;
if(k == 1) {
ll x, y, c;
c %= MOD;
cin >> x >> y >> c;
add_m(1, x, y, c);
}
if(k == 2) {
ll x, y, c;
c %= MOD;
cin >> x >> y >> c;
add_p(1, x, y, c);
}
if(k == 3) {
ll x, y;
cin >> x >> y;
cout << search(1, x, y) << endl;
}
}
return 0;
}
题目:小白逛公园
今天才发现题目理解出错了……前几天白干了。
# 分块
题目:题三、T170586 数列分块入门1
这道题首先是用线段树写的:
#include
using namespace std;
#define lsp p << 1
#define rsp p << 1 | 1
#define ll long long
const int N = 50010;
ll a[4 * N];
struct T {
int l, r;
ll num, plz;
T operator + (const T b) const {
T c;
c.plz = 0;
c.l = l, c.r = b.r;
c.num = num + b.num;
return c;
}
}t[4 * N];
void build(int p, int l, int r) {
if(l == r) {
t[p].l = l, t[p].r = r;
t[p].num = a[l];
return ;
}
int mid = (l + r) >> 1;
build(lsp, l, mid);
build(rsp, mid + 1, r);
t[p] = t[lsp] + t[rsp];
return ;
}
//子节点
void addplz(int p, ll plz) {
t[p].num += plz * (t[p].r - t[p].l + 1);
t[p].plz += plz;
}
void pushdown(int p) {
addplz(lsp, t[p].plz);
addplz(rsp, t[p].plz);
t[p].plz = 0;
}
//整个区间的元素加上k
void add(int p, int l, int r, ll k) {
//包含在内
if(l <= t[p].l && t[p].r <= r) {
t[p].num += k * (t[p].r - t[p].l + 1);
t[p].plz += k;
return ;
}
if(t[p].plz) pushdown(p);
if(t[lsp].r >= l) add(lsp, l, r, k);
if(t[rsp].l <= r) add(rsp, l, r, k);
t[p] = t[lsp] + t[rsp];
return ;
}
T search(int p, int pos) {
if(t[p].l == t[p].r) {
return t[p];
}
if(t[p].plz) pushdown(p);
if(pos <= t[lsp].r) return search(lsp, pos);
else return search(rsp, pos);
}
int main() {
// freopen("ttt.in", "r", stdin);
// freopen("ttt.out", "w", stdout);
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
build(1, 1, n);
int op, l, r; ll c;
for(int i = 1; i <= n; i ++) {
scanf("%d", &op);
if(op == 0) {
scanf("%d%d%lld", &l, &r, &c);
add(1, l, r, c);
}
else {
scanf("%d%d%lld", &l, &r, &c);
T C = search(1, r);
printf("%lld\n", C.num);
}
}
return 0;
}
然后是用区块做的
#include
using namespace std;
typedef long long ll;
const int N = 500010;
int L[N], R[N], pos[N];
ll a[N], add[N], sum[N];
int n;
int l, r; ll c;
void build() {
int len = sqrt(n);
int t = n / len;
for(int i = 1; i <= t; i ++) {
L[i] = (i - 1) * len + 1;
R[i] = i * len;
}
if(R[t] < n) t ++, L[t] = (t - 1) * len + 1, R[t] = n;
for(int i = 1; i <= t; i ++) {
for(int j = L[i]; j <= R[i]; j ++) {
pos[j] = i;
sum[i] += a[j];
}
}
}
void change(int l, int r, ll d) {
int q = pos[l], p = pos[r];
if(q == p) {
for(int i = l; i <= r; i ++) a[i] += d;
sum[q] += (r - l + 1) * d;
return ;
}
for(int i = q + 1; i <= p - 1; i ++) add[i] += d;
for(int i = l; i <= R[q]; i ++) a[i] += d;
sum[q] += (R[q] - l + 1) * d;
for(int i = L[p]; i <= r; i ++) a[i] += d;
sum[p] += (r - L[p] + 1) * d;
}
ll search (int p) {
return a[p] + add[pos[p]];
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
build();
for(int i = 1; i <= n; i ++) {
int op; scanf("%d", &op);
scanf("%d%d%lld", &l, &r, &c);
//询问a[i]
if(!op) {
change(l, r, c);
}
//区间加上某个值
else {
printf("%lld\n", search(r) );
}
}
return 0;
}
题目:T170589 数列分块入门 4
调了三个小时:
错误一:q,p
与l,r
混用
错误二:漏了处理最后一个分组
错误三:欣赏一下,错误时是这么写的:
for(int i = q + 1; i <= p - 1; i ++) {
sum[i] += (R[i] - L[i] + 1) * d;
plz[i] += d;
}
#include
using namespace std;
const int N = 500010;
typedef long long ll;
int n;
int L[N], R[N], pos[N];
ll a[N], sum[N], plz[N];
int len;
void build() {
len = sqrt(n);
int t = ceil((double)n / len);
for(int i = 1; i <= t; i ++) {
L[i] = (i - 1) * len + 1;
R[i] = len * i;
}
if(R[t] < n) t += 1, L[t] = R[t - 1] + 1, R[t] = n;//错误二事故发生处
for(int i = 1; i <= t; i ++)
for(int j = L[i]; j <= R[i]; j ++) {
pos[j] = i;
sum[i] += a[j];
}
return ;
}
void add(int l, int r, ll d) {
int q = pos[l], p = pos[r];
if(q == p) {
for(int i = l; i <= r; i ++) a[i] += d;//错误一事故发生处
sum[q] += (r - l + 1) * d;
return ;
}
for(int i = q + 1; i <= p - 1; i ++) plz[i] += d;//错误三事故发生处
for(int i = l; i <= R[q]; i ++) a[i] += d;
sum[q] += (R[q] - l + 1) * d;
for(int i = L[p]; i <= r; i ++) a[i] += d;
sum[p] += (r - L[p] + 1) * d;
return ;
}
ll search(int l, int r, ll M) {
int q = pos[l], p = pos[r]; ll ans = 0;
if(q == p) {
for(int i = l; i <= r; i ++) ans += a[i], ans %= M;//错误一事故发生处
ans += (r - l + 1) * plz[q], ans %= M;
return ans % M;
}
for(int i = q + 1; i <= p - 1; i ++) ans += sum[i] + (R[i] - L[i] + 1) * plz[i], ans %= M;
for(int i = l; i <= R[q]; i ++) ans += a[i], ans %= M;
ans += (R[q] - l + 1) * plz[q], ans %= M;
for(int i = L[p]; i <= r; i ++) ans += a[i], ans %= M;
ans += (r - L[p] + 1) * plz[p], ans %= M;
return ans % M;
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
build();
for(int i = 1; i <= n; i ++) {
int op; scanf("%d", &op);
int l, r; ll c;
scanf("%d%d%lld", &l, &r, &c);
if(op == 0) add(l, r, c);
else printf("%lld\n", search(l, r, c + 1));
}
return 0;
}
下一篇:点这里