Luogu P1471 方差

我都快忘记还有一种公式叫完全平方公式了…

$$ (a + b) ^ 2 = a ^ 2 + b ^ 2 + 2ab $$

对,就是它,这道题目的核心

0x01 分析题目

题目链接: [https://www.luogu.org/problemnew/show/P1471]

让我们看看什么是方差

对于一个长度为 $ n $ 的数列 $ A $ ,其方差为
$$ \frac{1}{n} \sum^{n}_{i=1}(A_i – k)^2 $$
其中,$ k $ 为 $ A $ 数列的平均数

emmm, 看起来好复杂啊,等一下,这里有个平方

$$
\begin{align}
\frac{1}{n} \sum^{n}_{i=1}(A_i – k)^2 &= \frac{1}{n} \sum^{n}_{i=1}{A_i ^ 2 + k ^ 2 + 2A_ik} \\
&= \frac{1}{n} [\sum^{n}_{i=1}{A_i ^ 2} + nk^2 + 2k(\sum^{n}_{i=1}A_i)]
\end{align}
$$

恩,舒服多了

看一下操作,区间加,区间平均数(区间求和),区间方差?

区间方差怎么求?

先回到刚才的式子

然后,$ nk^2 $可以直接线段树求和处理出来
$2k(\sum^{n}_{i=1}A_i)$ 同上

$ \sum^{n}_{i=1}{A_i ^ 2} $怎么办?

回到完全平方公式,我们求平方和的时候,给数字$A_i$加$x$相当于把这个原$A_i^2$加上 $ (A_i + x) ^ 2 – A_i ^ 2 = (A_i ^ 2 + x ^ 2 + 2A_ix) – A_i ^ 2 = x ^ 2 + 2A_ix $

这不归根结底还是求和线段树吗……直接双标记线段树即可

0x02 代码

#include <cstdio>
const int N = 110000;
int op, n, m, x, y;
double k, z, tmp_double;
struct node{
    double sum, square;
    void operator+=(const node &b){
        this -> sum += b.sum;
        this -> square += b.square;
    }
}tmp;
node tree[N << 2];
double lazy[N << 2];
inline void pushup(int now){
    tree[now].sum = tree[now << 1].sum + tree[now << 1 | 1].sum;
    tree[now].square = tree[now << 1].square + tree[now << 1 | 1].square;
}
inline void pushdown(int now, int lson, int rson){
    if(lazy[now]){
        tree[now << 1].square  = tree[now << 1].square + (lazy[now] * tree[now << 1].sum * 2.0) + (lazy[now] * lazy[now]) * lson;
        tree[now << 1].sum += lazy[now] * lson;
        tree[now << 1 | 1].square  = tree[now << 1 | 1].square + (lazy[now] * tree[now << 1 | 1].sum * 2.0) + (lazy[now] * lazy[now]) * rson;
        tree[now << 1 | 1].sum += lazy[now] * rson;
        lazy[now << 1] += lazy[now];
        lazy[now << 1 | 1] += lazy[now];
        lazy[now] = 0;
    }
}
inline void query_add(int now, int left, int rig, int from, int to, double val){
    if(from <= left && rig <= to){
        tree[now].square = tree[now].square + ((val * tree[now].sum ) * 2) + (val * val) * (rig - left + 1);
        tree[now].sum = tree[now].sum + val * (rig - left + 1);
        lazy[now] += val;
        return ;
    }
    int mid = (left + rig) >> 1;
    pushdown(now, mid - left + 1, rig - mid);
    if(from <= mid) query_add(now << 1, left, mid, from, to, val);
    if(to > mid) query_add(now << 1 | 1, mid + 1, rig, from, to, val);
    pushup(now);
}
inline node query_sum(int now, int left, int rig, int from, int to){
    if(from <= left && rig <= to){
        return tree[now];
    }
    int mid = (left + rig) >> 1; node res = (node){0, 0};
    pushdown(now, mid - left + 1, rig - mid);
    if(from <= mid) res += query_sum(now << 1, left, mid, from, to);
    if(to > mid) res += query_sum(now << 1 | 1, mid + 1, rig, from, to);
    return res;
}
int main(){
#ifdef woshiluo
    freopen("luogu.1471.in", "r", stdin);
    freopen("luogu.1471.out", "w", stdout);
#endif
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++){
        scanf("%lf", &z);
        query_add(1, 1, n, i, i, z);
    }
    while(m--){
        scanf("%d", &op);
        if(op == 1){
            scanf("%d%d%lf", &x, &y, &z);
            k += (y - x + 1) * z;
            query_add(1, 1, n, x, y, z);
        }
        else if(op == 2){
            scanf("%d%d", &x, &y);
            tmp = query_sum(1, 1, n, x, y);
            printf("%.4lf\n", tmp.sum / (y - x + 1));
        }
        else if(op == 3){
            scanf("%d%d", &x, &y);
            tmp = query_sum(1, 1, n, x, y);
            printf("%.4lf\n", ( (tmp.square + (tmp.sum / (y - x + 1)) * tmp.sum - ( 2 * tmp.sum * (tmp.sum / (y - x + 1)) ) ) / (y - x + 1) ) );
        }
    }
}
]]>

Codeforces Round #538 (Div. 2) 解题报告

A Got Any Grapes?

顺序判断即

比赛的时候忘记写 else 既然 PP 了,然后就 FST

#include <cstdio>
int an,dm,mi;
int gr,pu,bl;
int main(){
    scanf("%d%d%d", &an, &dm, &mi);
    scanf("%d%d%d", &gr, &pu, &bl);
    if(an > gr) {
        printf("NO\n");
        return 0;
    }
    else gr -= an;
    if(pu + gr < dm){
        printf("NO\n");
        return 0;
    }
    else {
        if(pu <= dm) {dm -= pu; pu = 0;}
        else {pu -= dm; dm = 0;}
        if(gr < dm) {
            printf("NO\n");
            return 0;
        }
        else gr -= dm;
    }
    if(gr + pu + bl < mi) printf("NO\n");
    else printf("YES\n");
}

B Yet Another Array Partitioning Task

反正最后还是要前$ m \times k $ 个数字

直接离散化,然后见当前区间有$ m $ 个就收

#include <cstdio>
#include <algorithm>
const long long N = 2e5 + 1e4;
struct node{
    long long now, id;
}b[N];
long long n, m, k, cnt, time;
long long a[N];
bool vis[N];
bool cmp(node a, node b){
    return a.now > b.now;
}
int main(){
    scanf("%lld%lld%lld", &n, &m, &k);
    for(long long i = 1; i <= n; i++){
        scanf("%lld", &a[i]);
        b[i].now = a[i], b[i].id = i;
    }
    std::sort(b + 1, b + n + 1, cmp);
    long long tmp = m * k;
    for(long long i = 1; i <= tmp; i++) vis[ b[i].id ] = true, cnt += b[i].now;
    printf("%lld\n",cnt);
    cnt = 0;
    for(long long i = 1; i <= n; i++){
        cnt += vis[i];
        if(cnt == m){
            time++;
            time < k && printf("%lld ",i);
            cnt = 0;
        }
    }
}

C. Trailing Loves (or L’oeufs?)

求 $ b $ 进制下 $ n! $ 的末尾的 0 的个数

显然我们要求中间乘出来有多少个$b$

接下来就是分解质因数,然后枚举求最小值即可

#include <cstdio>
#include <cmath>
const long long INF = 1e18 + 1e17;
inline long long Min(long long a, long long b){return a < b? a: b;}
struct node{
    long long now, cnt;
}pri[(int)(1e6)];
int pcnt;
long long n, b, tmp, cnt, ans = INF;
inline void get_pri(long long b){
    tmp = std::sqrt(b);
    for(long long i = 2; i <= tmp; i++){
        if(b % i == 0){
            pri[ ++pcnt ] = (node){i, 0};
            while(b % i == 0){
                pri[pcnt].cnt++;
                b/=i;
            }
        }
    }
    if(b != 1) pri[ ++pcnt ] = (node){b, 1};
}
int main(){
    scanf("%lld%lld", &n, &b);
    get_pri(b);
    for(int i = 1; i <= pcnt; i++){
        tmp = 1; cnt = 0;
        while(tmp * pri[i].now <= n){
            tmp *= pri[i].now;
            if(tmp < 0 || tmp % pri[i].now != 0) break;
            cnt += n / tmp;
        }
        ans = Min(ans, cnt/pri[i].cnt);
    }
    printf("%lld\n", ans);
}

D. Flood Fil

有一个非常显然的地方,就是我们每次有两个状态,当前联通部分向左对其或者想右对其

然后我们先把数列中的联通部分预处理出来,然后区间 dp 即可

#include <cstdio>
#include <cstring>
inline int Min(int a, int b){return a < b? a: b;}
const int N = 5100;
int n;
int a[N], l[N], r[N], f[N][N];
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    l[1] = 1;
    for(int i = 2; i <= n; i++){
        if(a[i] == a[i - 1]) l[i] = l[i - 1];
        else l[i] = i;
    }
    r[n] = n;
    for(int i = n - 1; i >= 1; i--){
        if(a[i] == a[i + 1]) r[i] = r[i + 1];
        else r[i] = i;
    }
    memset(f, 0x3f, sizeof(f));
    for(int i = 1; i<= n;i ++) f[ l[i] ][ r[i] ] = 0;
    for(int len = 0; len < n; len++){
        for(int i = 1,j; i + len <= n; i++){
            j = i + len;
            if(i > 1 && j < n && a[i - 1] == a[j + 1])
                f[ l[i - 1] ][ r[j + 1] ] = Min(f[ l[i - 1] ][ r[j + 1] ],f[i][j] + 1);
            if(i > 1)
                f[ l[i - 1] ][j] = Min(f[ l[i - 1] ][j], f[i][j] + 1);
            if(j < n)
                f[i][ r[j + 1] ] = Min(f[i][ r[j + 1] ], f[i][j] + 1);
        }
    }
    printf("%d", f[1][n]);
}

E. Arithmetic Progression

交互题目,60次询问内知道当前乱序等差数列的首项和公差

先二分找最大的,然后random_shuffle随机化询问,求与最大项差的 gcd 即为公差

#include <cstdio>
#include <algorithm>
int gcd(int a, int b){return b? gcd(b ,a%b): a;}
inline int Min(int a, int b){return a < b? a: b;}
inline int Aabs(int a){return a < 0? (0 - a): a;}
const int N = 1e6 + 1e5;
int n, global_tmp, d, max, chance_cnt = 60;
int a[N];
inline bool has_num(int now){
    printf("> %d\n",now);
    fflush(stdout);
    scanf("%d", &global_tmp);
    chance_cnt--;
    return global_tmp;
}
inline int binary_search_max(int max_limit){
    int left = 0, rig = max_limit, mid, res;
    while(left <= rig){
        mid = (left + rig) >> 1;
        if( has_num(mid) ) left = mid + 1;
        else rig = mid - 1, res = mid;
    }
    return res;
}
int main(){
    scanf("%d", &n);
    max = binary_search_max(1e9);
    for(int i = 1; i <= n; i++) a[i] = i;
    for(int i = 1; i <= 5; i++) std::random_shuffle(a + 1, a + n + 1);
    for(int i = 1; i <= Min(n ,chance_cnt); i++){
        printf("? %d\n", a[i]);
        fflush(stdout);
        scanf("%d", &global_tmp);
        if(global_tmp == max) continue;
        if(d == 0) d = Aabs(max-global_tmp);
        d = gcd(d , Aabs(max-global_tmp));
    }
    fflush(stdout);
    printf("! %d %d\n", max - (n - 1) * d, d);
}

F. Please, another Queries on Array?

这个题目首先得知道$\varphi(n)$的求法

然后就是乘积线段树和或线段树维护一下

就没有然后了

#include <cstdio>
const int N = 4e5 + 1e4;
const long long mod = 1e9 + 7;
int n, q, x, y, z, pcnt;
int a[N];
long long p[310], pri[N], inv[310];
char op[10];
inline long long ksm(long long a, long long p){
    long long res = 1;
    while(p){
        if(p & 1) res = (res * a) % mod;
        a = (a * a) % mod;
        p >>= 1;
    }
    return res;
}
inline void prime(){
    for(int i = 2; i <= 300; i++){
        for(int j = 1; j <= pcnt; j++)
            if(i % p[j] == 0) pri[i] |= pri[ p[j] ];
        if(pri[i] == 0) {p[++pcnt] = i; pri[i] = (1LL << (pcnt - 1LL));}
    }
}
inline void get_inv(){
    for(int i = 1; i <= pcnt; i++)
        inv[i] = ksm(p[i], mod - 2);
}
// Segment Tree Start
struct node{
    long long val,pri;
    void operator+= (const node &b){
        this -> val = (this -> val * b.val) % mod;
        pri |= b.pri;
    }
}tree[N << 2], lazy[N << 2];
inline void pushup(int now){
    tree[now].val = (tree[now << 1].val * tree[now << 1 | 1].val) % mod;
    tree[now].pri = tree[now << 1].pri | tree[now << 1 | 1].pri;
}
inline void pushdown(int now, int lson, int rson){
    if(lazy[now].pri != 0){
        tree[now << 1].val  =  (1LL * tree[now << 1].val * ksm(lazy[now].val, lson)) % mod;
        tree[now << 1 | 1].val  =  (1LL * tree[now << 1 | 1].val * ksm(lazy[now].val, rson)) % mod;
        tree[now << 1].pri |= lazy[now].pri;
        tree[now << 1 | 1].pri |= lazy[now].pri;
        lazy[now << 1].val = (1LL * lazy[now << 1].val * lazy[now].val) % mod;
        lazy[now << 1 | 1].val = (1LL * lazy[now << 1 | 1].val * lazy[now].val) % mod;
        lazy[now << 1].pri |= lazy[now].pri;
        lazy[now << 1 | 1].pri |= lazy[now].pri;
        lazy[now].val = 1LL; lazy[now].pri = 0;
    }
}
inline void query_mut(int now, int left, int rig, int from, int to, int val){
    if(from <= left && rig <= to){
        tree[now].val = (1LL * tree[now].val * ksm(val, (rig - left + 1LL))) % mod;
        tree[now].pri |= pri[val];
        lazy[now].val = (lazy[now].val * val) % mod;
        lazy[now].pri |= pri[val];
        return ;
    }
    int mid = (left + rig) >> 1;
    pushdown(now, mid - left + 1LL, rig - mid);
    if(from <= mid) query_mut(now << 1, left, mid, from, to, val);
    if(to > mid) query_mut(now << 1 | 1, mid + 1, rig, from, to, val);
    pushup(now);
}
inline node query_sum(int now, int left, int rig, int from, int to){
    if(from <= left && rig <= to) return tree[now];
    int mid = (left + rig) >> 1;
    node res = (node){1, 0};
    pushdown(now, mid - left + 1LL, rig - mid);
    if(from <= mid) res += query_sum(now << 1, left, mid, from, to);
    if(to > mid) res += query_sum(now << 1 | 1, mid + 1, rig, from, to);
    pushup(now);
    return res;
}
inline void build_tree(int now, int left, int rig){
    lazy[now].val = 1; lazy[now].pri = 0;
    if(left == rig){
        scanf("%lld", &tree[now].val);
        tree[now].pri = pri[ tree[now].val ];
        return ;
    }
    int mid = (left + rig) >> 1;
    build_tree(now << 1, left, mid);
    build_tree(now << 1 | 1, mid + 1, rig);
    pushup(now);
}
// Segment Tree End
int main(){
    prime();
    get_inv();
    scanf("%d%d", &n, &q);
    build_tree(1, 1, n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    while(q--){
        scanf("%s", op);
        if(op[0] == 'M'){
            scanf("%d%d%d", &x, &y, &z);
            query_mut(1, 1, n, x, y, z);
        }
        else {
            scanf("%d%d", &x, &y);
            node tmp = query_sum(1, 1, n, x, y);
            for(int i = 1; i <= pcnt; i++){
                if((tmp.pri >> (i - 1LL)) & 1LL)
                    tmp.val = (((tmp.val * (p[i] - 1) ) % mod) * inv[i]) % mod;
            }
            printf("%lld\n", tmp.val);
        }
    }
}


]]>

科学解决 RMQ 问题 — 线段树/(ST/稀疏表)/分块

RBQRMQ

区间最值问题
大概就是那种给你一串数字,然后问你在区间[l,r]内,求最小值或最大值 一般,我们的操作,就是暴力枚举 然后你就光荣的TLE/MLE 然后让我们来说说区间问题的三种解法—线段树/ST表与分块

线段树


预处理: O( n*log(n) )
查询: O( log(n) )
单点更新: 支持,不破坏查询复杂度
写的爽,调的更爽
线段树是一种数据结构 首先我们需要了解一种东西,哈弗曼树 显然,对于一颗二叉树,如果一个节点号为n那么它的左节点是2n,右节点是2n+1 然后我们就可以愉快的存树了 然后什么是线段树呢? 吃的 它是能够维护一定区间的树 他的根节点是[L,R](最左和最右)的最优值 然后左节点是[L,(L+R)/2]的最优值 右节点是[(L+R)/2+1,R]的最优值 往后以此类推 直到l==r为止 简单代码如下:
这个代码对应[https://www.luogu.org/problemnew/show/P4644]这道题
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m,e;
struct whi{
    int left,rig,cost;
}a[11000];
int f[110000];
int tree[11000000];
bool cmp(whi a,whi b){
    return a.rig<b.rig;
}
// 创建
void build(int p,int l,int r){// p 当前下标 l 左端点 r 右端点
    if(l==r) {tree[p]=f[l];return ;}
    build(p<<1,l,(l+r)>>1);
    build(p<<1|1,((l+r)>>1)+1,r);
    tree[p]=min(tree[p<<1],tree[p<<1|1]);
}
// 更新单点
void update(int p,int l,int r,int now,int val){// p 当前下表 l 当前左端点 r 当前右端点 now 当前点的边界 val 更新的值
    if(l==r&&l==now){tree[p]=val;return ;}
    if(now<=((l+r)>>1)) update(p<<1,l,(l+r)>>1,now,val);
    else update(p<<1|1,((l+r)>>1)+1,r,now,val);
    tree[p]=min(tree[p<<1],tree[p<<1|1]);
}
// 求区间最值
int query(int p,int l,int r,int ll,int rr){//p 当前下表  l 当前左端点 r 当前右端点 ll 需求左端点 rr 需求右端点
    if(ll<=l&&rr>=r){return tree[p];}
    int ans=0x7fffffff,mid=(l+r)>>1;
    if(ll<=mid) ans=min(ans,query(p<<1,l,mid,ll,rr));
    if(rr>mid) ans=min(ans,query(p<<1|1,mid+1,r,ll,rr));
    return ans;
}
int main(){
    memset(f,0x3f,sizeof(f));
    scanf("%d%d%d",&n,&m,&e);
    m++;e++;
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&a[i].left,&a[i].rig,&a[i].cost);
        a[i].left++;a[i].rig++;
    }
    sort(a+1,a+n+1,cmp);
    /*for(int i=1;i<=n;i++){
        printf("%d %d %d\n",a[i].left,a[i].rig,a[i].cost);
    }*/
    f[m-1]=0;
    build(1,m-1,e);
    for(int i=1;i<=n;i++){
        if(a[i].rig<m) continue;
        if(a[i].left<=m) f[a[i].rig]=min(f[a[i].rig],a[i].cost);
        else f[a[i].rig]=min(f[a[i].rig],query(1,m-1,e,a[i].left-1,a[i].rig)+a[i].cost);
        update(1,m-1,e,a[i].rig,f[a[i].rig]);
    }
    if(f[e]>=1061109567) printf("-1");
    else printf("%d",f[e]);
}
当然这个并不是线段树的完整用法wq 更详细的用法,请看下篇题解

ST/稀疏表

> 速度虽快,更新却难
预处理: O( n*log(n) )
查询: O( 1 )
单点更新: 支持,需重新预处理
ST/稀疏表(以下统称)ST表,是一种基于倍增思想的玄学算法 比如说我们用f[i][j]储存从 i 位置开始的 2^j个数中的最大值 那么转移的时候我们可以把当前区间拆成两个区间并分别取最大值 查询先计算出 log2(区间长度) 然后对于左端点和右端点分别进行查询,这样可以保证一定可以覆盖查询的区间 然后你就会发现一堆log和换底公式出现于你的程序中 感性理解吧~ 但是不难理解
对应例题:[https://www.luogu.org/problemnew/show/P3865]
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m,l,r;
int a[110000];
int f[110000][110];
int st_query(int l,int r){
    int k=log(r-l+1)/log(2);
    return max(f[l][k],f[r-(1<<k)+1][k]);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        f[i][0]=a[i];
    }
    int t=log(n)/log(2)+1;
    for(int j=1;j<t;j++){
        for(int i=1;i<=n-(1<<j)+1;i++){
            f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
        }
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d",&l,&r);
        printf("%d\n",st_query(l,r));
    }
}
看起来是不是相当简单qwq 但是要千万注意log时该加1时不该加时不加1

分块


高端的模拟
预处理: O(n)
查询: O(sqrt(n))
更新: O(sqrt(n))
先说说什么是分块算法吧 分块,顾名思义,就是将一个数列分成几块单独处理,比如讲[1,5]分成三块就是[1,2] [3,4] [5]这三个 那么我们查找[1,3]的最小值,只需要查找第一个与第二个块的最小值 经过一些证明,我们可以知道,在分成sqrt(n)块的情况下最优 所以就有了复杂度中的一堆sqrt(n) 那么代码我相信大家心里都有数了 那么和线段树一样,代码下篇题解讲,毕竟都是一道题目嘛wq

End


这几种解法,只是一种板子,而真正的RMQ算法,可能会结合多种东西,或者被多种东西所结合,所以说活学活用是最重要的,无论如何,刷题是最重要的 “Happy Coding Life” ]]>