bzoj1558 [JSOI2009]等差数列

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1558

【题解】

这题恶心死人了啊。。

网络上题解很多都是看代码看代码。。真是太不负责任了。。我这里详细说一下吧。。

题解在代码下面。

# include <stdio.h>
# include <string.h>
# include <iostream>
# include <algorithm>
// # include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int M = 5e5 + 10;
const int mod = 1e9+7;

# define RG register
# define ST static

int n, a[M], b[M];

struct pa {
    int s, ll, rr, sz;
    // 连续(包括中间散的),左散,右散
    long long l, r;
    pa() {}
    pa(int s, int ll, int rr, int sz, long long l, long long r) : s(s), ll(ll), rr(rr), sz(sz), l(l), r(r) {}
    friend pa operator + (pa a, pa b) {
        pa c; int f = (a.r == b.l);
        c.s = a.s + b.s; c.sz = a.sz + b.sz;
        c.l = a.l, c.r = b.r;
        if(a.s == 0 && b.s == 0) {
            if(!f) c.ll = c.rr = c.sz;
            else {
                c.ll = a.ll-1;
                c.rr = b.rr-1;
                ++c.s;
            }
            return c;
        }
        if(a.s == 0) {
            c.rr = b.rr;
            if(!f) c.ll = a.sz + b.ll;
            else {
                c.ll = a.ll-1;
                if(b.ll > 0) c.s += (b.ll-1)/2 + 1;
            }
            return c;
        }
        if(b.s == 0) {
            c.ll = a.ll;
            if(!f) c.rr = a.rr + b.sz;
            else {
                c.rr = b.rr-1;
                if(a.rr > 0) c.s += (a.rr-1)/2 + 1;
            }
            return c;
        }

        c.ll = a.ll, c.rr = b.rr;

        if(a.rr == 0 && b.ll == 0) {
            if(f) --c.s;
            return c;
        }
        if(a.rr == 0) {
            if(f) c.s += (b.ll-1)/2;
            else c.s += b.ll/2;
            return c;
        }
        if(b.ll == 0) {
            if(f) c.s += (a.rr-1)/2;
            else c.s += a.rr/2;
            return c;
        }

        int d = (a.rr + b.ll)/2;
        if(f) d = min(d, 1 + (a.rr-1)/2 + (b.ll-1)/2);

        c.s += d;

        return c;
    }
};

namespace SMT {
    pa w[M];
    ll tag[M];
    # define ls (x<<1)
    # define rs (x<<1|1)
    inline void up(int x) {
        if(!x) return;
        if(!rs) {w[x] = w[ls]; return;}
        if(!ls) {w[x] = w[rs]; return;}
        w[x] = w[ls] + w[rs];
    }
    inline void pushtag(int x, ll d) {
        w[x].l += d, w[x].r += d;
        tag[x] += d;
    }
    inline void down(int x) {
        if(!x) return;
        if(!tag[x]) return;
        pushtag(ls, tag[x]);
        pushtag(rs, tag[x]);
        tag[x] = 0;
    }
    inline void build(int x, int l, int r) {
        tag[x] = 0;
        if(l == r) {
            w[x] = pa(0, 1, 1, 1, b[l], b[l]);
            return ;
        }
        int mid = l+r>>1;
        build(ls, l, mid);
        build(rs, mid+1, r);
        up(x);
    }
    inline void edt(int x, int l, int r, int L, int R, ll d) {
        if(L <= l && r <= R) {
            pushtag(x, d);
            return ;
        }
        down(x);
        int mid = l+r>>1;
        if(L <= mid) edt(ls, l, mid, L, R, d);
        if(R > mid) edt(rs, mid+1, r, L, R, d);
        up(x);
    }
    inline pa query(int x, int l, int r, int L, int R) {
        if(L <= l && r <= R) return w[x];
        down(x);
        int mid = l+r>>1;
        if(R <= mid) return query(ls, l, mid, L, R);
        else if(L > mid) return query(rs, mid+1, r, L, R);
        else return query(ls, l, mid, L, mid) + query(rs, mid+1, r, mid+1, R);
    }
    inline void debug(int x, int l, int r) {
        printf("x=%d, l=%d, r=%d:  sum = %d, size = %d, left = %d, right = %d, lnum = %lld, rnum = %lld\n", x, l, r, w[x].s, w[x].sz, w[x].ll, w[x].rr, w[x].l, w[x].r);
        if(l==r) return;
        down(x);
        int mid = l+r>>1;
        debug(ls, l, mid);
        debug(rs, mid+1, r);
    }
}

int main() {
    int Q, l, r, a1, d;
    char opt[23];
    pa t;
    cin >> n;
    for (int i=1; i<=n; ++i) scanf("%d", a+i);
    for (int i=1; i<n; ++i) b[i] = a[i+1] - a[i];
//    for (int i=1; i<n; ++i) printf("%d ", b[i]); puts("");
    cin >> Q;
    if(n == 1) {
        while(Q--) {
            scanf("%s", opt);
            if(opt[0] == ‘A‘) scanf("%*d%*d%*d%*d");
            if(opt[0] == ‘B‘) {scanf("%*d%*d"); puts("1");}
        }
        return 0;
    }
    SMT::build(1, 1, n-1);
//    SMT::debug(1, 1, n-1);
    while(Q--) {
        scanf("%s", opt);
        if(opt[0] == ‘A‘) {
            scanf("%d%d%d%d", &l, &r, &a1, &d);
            if(l != 1) SMT::edt(1, 1, n-1, l-1, l-1, a1);
            if(l <= r-1) SMT::edt(1, 1, n-1, l, r-1, d);
            if(r != n) SMT::edt(1, 1, n-1, r, r, -1ll * (r-l)*d - a1);
        }
        if(opt[0] == ‘B‘) {
            scanf("%d%d", &l, &r);
            if(l == r) puts("1");
            else {
                t = SMT::query(1, 1, n-1, l, r-1);
                int ans = (r-l+1+1)/2;
                if(t.s == 0) printf("%d\n", ans);
                else {
                    ans = min(ans, t.s + (t.ll+1)/2 + (t.rr+1)/2);
                    printf("%d\n", ans);
                }
            }
        }
//        SMT::debug(1, 1, n-1);
    }
    return 0;
}

就是这里这里是题解啦!

首先一段加等差数列。这个可以用线段树直接维护,但是为了询问方便,我们选择线段树维护差分数组

比如:

n=6, a[]={1,2,4,7,11,16}

那么维护的就是b[]={1,2,3,4,5},b[i] = a[i+1] - a[i]

对于b建立线段树进行维护。

由于我们发现n=1没有差分。。要特判下。

那么现在我们考虑修改:在[a,b]上加首项a1公差d的等差数列。由于我们维护的是差分数组,这个就很好办了。

假装[a,b]很长,那么中间的部分差分增加的是公差d(前一个+x+d,后一个+x+d+d)。

稍微推导下(这部分可以自己举例子,自行算算,特别是边界条件),即可得到:

修改:b[l-1] += a1, b[l...r-1] += d, b[r] -= (r-l)*d+a1

这里需要判断:如果我们修改了区间[1,?],那么b[l-1]不需要改,因为第一个地方没有差分。

如果我们修改了区间[x,x],就不需要进行第二个修改。等等。

至于第三个减,是为了维护后面差分稳定,容易看出a[r] += (r-l)*d+a1,所以a[r+1]-a[r]要减少(r-1)*d+a1

我们讨论完了修改我们开始讨论询问吧。

询问求的是最少分成多少个等差数列。由于是段,连续的,就比序列好办多了。

这不是简单求[l,r]区间的差分数组有多少个连续的段的问题。

因为,比如很简单的例子:

差分数组为b[]={1,2,3,4},a[]={x,x+1,x+3,x+6,x+10}

那么答案不是4段,而是3段,为什么?因为{x,x+1}和{x+3,x+6}和{x+10}都是等差,所以我们要判断2个数构成等差这个情况。

所以我们要多维护东西。

首先可以确定的是,一个区间的中间部分如果确定是哪些等差数列,以后是不会改变的。

所以我们维护:

1. 区间中的等差数列段数。

2. 区间左边剩下的零散数的个数,以及右边剩下的零散数的个数

3. 区间左边的数是什么,区间右边的数是什么

4. 区间大小。

假设区间为[l,r],那么我们最后的答案就是线段树查询[l,r-1](因为差分过了所以要-1)出来后:

1. 要么是(r-l+1+1)/2(r-l+1为区间长度,这种分法是两个数两个数分)

2. 要么是询问出来的区间中的等差数列段数加上区间左边剩下的零散数的个数,以及右边剩下的零散数的个数按照1中方法分成等差数列的个数。

至于两边的个数怎么对应到段上,可以通过画几个例子来解决。

好了现在只要考虑线段树内和查询时,数据合并了。

 1 struct pa {
 2     int s, ll, rr, sz;
 3     // 连续(包括中间散的),左散,右散
 4     long long l, r;
 5     pa() {}
 6     pa(int s, int ll, int rr, int sz, long long l, long long r) : s(s), ll(ll), rr(rr), sz(sz), l(l), r(r) {}
 7     friend pa operator + (pa a, pa b) {
 8         pa c; int f = (a.r == b.l);
 9         c.s = a.s + b.s; c.sz = a.sz + b.sz;
10         c.l = a.l, c.r = b.r;
11         if(a.s == 0 && b.s == 0) {
12             if(!f) c.ll = c.rr = c.sz;
13             else {
14                 c.ll = a.ll-1;
15                 c.rr = b.rr-1;
16                 ++c.s;
17             }
18             return c;
19         }
20         if(a.s == 0) {
21             c.rr = b.rr;
22             if(!f) c.ll = a.sz + b.ll;
23             else {
24                 c.ll = a.ll-1;
25                 if(b.ll > 0) c.s += (b.ll-1)/2 + 1;
26             }
27             return c;
28         }
29         if(b.s == 0) {
30             c.ll = a.ll;
31             if(!f) c.rr = a.rr + b.sz;
32             else {
33                 c.rr = b.rr-1;
34                 if(a.rr > 0) c.s += (a.rr-1)/2 + 1;
35             }
36             return c;
37         }
38
39         c.ll = a.ll, c.rr = b.rr;
40
41         if(a.rr == 0 && b.ll == 0) {
42             if(f) --c.s;
43             return c;
44         }
45         if(a.rr == 0) {
46             if(f) c.s += (b.ll-1)/2;
47             else c.s += b.ll/2;
48             return c;
49         }
50         if(b.ll == 0) {
51             if(f) c.s += (a.rr-1)/2;
52             else c.s += a.rr/2;
53             return c;
54         }
55
56         int d = (a.rr + b.ll)/2;
57         if(f) d = min(d, 1 + (a.rr-1)/2 + (b.ll-1)/2);
58
59         c.s += d;
60
61         return c;
62     }
63 };

我们看上述代码就是线段树内结构体以及怎么合并。

首先sz, l,r都直接合并。

然后要考虑的就是ll和rr还有s要怎么合并的问题了。

先处理左边/右边没有连续段的问题(都是散的)

分三类:都没有,左没有,右没有

这部分直接看,是11~37行

现在左边/右边都有连续段了,要处理的是:

左边段紧挨着边界这种情况,也就是ll=0或rr=0这种情况。

分三类:a.rr=0&&b.ll=0,a.rr=0,b.ll=0

第一类显然两个段如果a.r=b.l的话就能直接合并成一个段了,否则就正常合并,在第41~44行。

后两类,如果a.r=b.l的时候,剩下的一个(大于0的b.ll或a.rr)就可以少一个数进行划分了。在第45~54行。

否则,就按照正常的划分方法:要么全部划分成2个2个的,要么把中间相等的提出来,左右划分。

懒得讨论提出来是不是一定优了。。就取min呗

然后……呼终于说完了

这题挺好的。。细节贼多

时间: 2024-10-06 01:15:30

bzoj1558 [JSOI2009]等差数列的相关文章

[JSOI2009]等差数列

第一篇博客哒 题面 https://www.luogu.com.cn/problem/P4243 首先看到区间加等差数列我们可以首先想到使用差分数组 就是记一个bi=ai+1-ai 然后每次修改al 到ar就只用将bl-1,br单点修改,bl至br-1区间修改就可以了 区间修改?我们首先想到了线段树 线段树可以维护是否为等差数列吗??? 可以! 每个结点维护上啥线段树必要的l,r,lazy标记什么的在多维护一个结构体val val 里面有什么呢? 首先为了下面转移方便维护区间最左边lv,最右边的

计划做题列表

估计是做不完.. emmmm  不用估计,是肯定 马上就滚回去学文化课了.... ....列表全都是数据结构... mdzz 我可能是傻了 1.数学  P1214 [USACO1.4]等差数列 Arithmetic Progressions https://www.luogu.org/problemnew/show/P1214 2.P4243 [JSOI2009]等差数列 线段树 https://www.luogu.org/problemnew/show/P4243 主席树 P3313 [SDO

51nod1055 最长等差数列

基准时间限制:2 秒 空间限制:262144 KB 分值: 80 N个不同的正整数,找出由这些数组成的最长的等差数列. 例如:1 3 5 6 8 9 10 12 13 14 等差子数列包括(仅包括两项的不列举) 1 3 5 1 5 9 13 3 6 9 12 3 8 13 5 9 13 6 8 10 12 14 其中6 8 10 12 14最长,长度为5. Input 第1行:N,N为正整数的数量(3 <= N <= 10000). 第2 - N+1行:N个正整数.(2<= A[i] &

【HDOJ 4768】 Flyer (等差数列+二分)

[HDOJ 4768] Flyer (等差数列+二分) Flyer Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2022    Accepted Submission(s): 743 Problem Description The new semester begins! Different kinds of student soc

51nod1056 最长等差数列 V2

基准时间限制:8 秒 空间限制:131072 KB 分值: 1280 N个不同的正整数,从中选出一些数组成等差数列. 例如:1 3 5 6 8 9 10 12 13 14 等差子数列包括(仅包括两项的不列举) 1 3 5 1 5 9 13 3 6 9 12 3 8 13 5 9 13 6 8 10 12 14 其中6 8 10 12 14最长,长度为5. 现在给出N个数,你来从中找出一个长度 >= 200 的等差数列,如果没有,输出No Solution,如果存在多个,输出最长的那个的长度. I

HDU 5512 Pagodas(等差数列)

题目戳这 题意:给你三个数:n,a,b,一开始一个集合里面有两个数:a和b,然后两个人轮流往这个集合里面增加数字,增加的这个数字的原则是,这个集合里面任选两个数的和或差,集合里面的数字不能重复,同时这个数字不能大于 n .(本来说的造塔,这样说方便一点) 思路:本来还以为是博弈,但是后来把数字的加减都模拟一遍之后发现,无论怎样,最后得到的这个集合里面的数列,其实是一个等差数列,所以这就简单了,由一开得到的 a 和 b 来相减,然后不断地取最小的两个数相减,然后等到等差数列的差,这个差同时也是等差

1452: [JSOI2009]Count

1452: [JSOI2009]Count Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 2083  Solved: 1228[Submit][Status][Discuss] Description Input Output Sample Input Sample Output 1 2 HINT 思路:二维树状数组: 将数组开三维的然后,每一维维护一个值,然后直接二维树状数组维护即可: 1 #include<stdio.h> 2 #includ

1055 最长等差数列

1055 最长等差数列 基准时间限制:2 秒 空间限制:262144 KB N个不同的正整数,找出由这些数组成的最长的等差数列. 例如:1 3 5 6 8 9 10 12 13 14 等差子数列包括(仅包括两项的不列举) 1 3 5 1 5 9 13 3 6 9 12 3 8 13 5 9 13 6 8 10 12 14 其中6 8 10 12 14最长,长度为5. Input 第1行:N,N为正整数的数量(3 <= N <= 10000). 第2 - N+1行:N个正整数.(2<= A

找最长等差数列的长度

题目描述:给定n(1<=n<=100)个数,从中找出尽可能多的数,使得他们能够组成一个等差数列.求最长的等差数列的长度,每个数的绝对值不超过10000000. 样例: 输入数组为:2,8,3,5,6,4 输出为:5 Java代码实现: 1 import java.util.*; 2 public class Main { 3 public static int maxlength(int a[]){ 4 int n=a.length; 5 int large=0; //记录最大长度 6 Arr