二分+三分+莫队全纪录

二分+三分

二分+三分讲解

二分是一种很强的方法,不要因为ta太普遍而忽视
这里指出ta的必要条件:

  • 答案具有单调性

  • 已知答案的情况下,可以判定答案的可行性

也就是说,如果我们发现一个问题不好直接求解,但是我们可以想办法判定一个解得正确性,那么就可以考虑二分

三分实际上就是凸函数上的“二分”
一般用于凸包,二次函数等的最值求解
可能有些题目的凸性不那么明显,那么我们就可以手玩一下,进行简单的判断

经典例题:二分+并查集
经典例题:三分+秦九韶算法
经典例题:线段树分治+凸包+三分
经典例题:分块+凸包+三分

代码变化比较多,只能给出一些比较典型的

//二分最大值 
int EF(int l,int r) {
    int ans=0;
    while (l<=r) {
        int mid=(l+r)>>1;
        if (check(mid)) ans=max(ans,mid),l=mid+1;
        else r=mid-1;
    } 
    return ans;
} 

//二分最大值(double)
const double eps=1e-8;

double EF(double l,double r) {
    double ans=0;
    while (r-l>=eps) {
        double mid=(l+r)/2.0;
        if (check(mid)) l=mid;
        else r=mid;
    }
    return (l+r)/2.0;
}

//三分上凸函数
int SF(int l,int r) {
    int m1,m2;
    while (l<r-1) {
        m1=(l+r)>>1;
        m2=(m1+r)>>1;
        if (f(m1)<f(m2)) l=m1;
        else r=m2;
    }
    return f(l)>f(r)? l:r;
} 

int SF(int l,int r) {
    int m1,m2;
    while (l<=r) {
        if (r-l<=2) {
            if (r-l==0) return f(l);
            if (r-l==1) return max(f(l),f(l+1));
            if (r-l==2) return max(f(l),max(f(l+1),f(l+2)));
            break;
        }
        m1=l+(r-l)/3;
        m2=r-(r-l)/3;
        if (f(m1)<f(m2)) l=m1;
        else r=m2;
    }
}

//三分上凸函数(double) 
double SF(double l,double r) {
    double m1,m2;
    while (r-l>=eps) {
        m1=l+(r-l)/3.0;
        m2=r-(r-l)/3.0;
        if (f(m1)<f(m2)) l=m1;
        else r=m2;
    }
    return (m1+m2)/2.0;
}

莫队

莫队讲解
带修改的树上莫队

经典例题:序列上的莫队
经典例题:带修改的莫队
经典例题:不带修改的树上莫队

莫队简直就是暴力的王者,优异而且简单易学
可能有一个小缺点:一般需要一个巨大的数组记录类似颜色数之类的东西

注意

莫队的分块

int cmp(const node &a,const node &b) 
{
    if (a.block!=b.block) return a.block<b.block;
    else return a.y<b.y;
}

Q[i].block=(Q[i].x-1)/unit+1;

树上莫队

树上路径的莫队实际上就是搞出一个 d f s
根据路径 ( u , v ) 的形态不同,询问不同的区间:

  • l c a ( u , v ) = u | | v [ s t [ u ] , s t [ v ] ]

  • l c a ( u , v ) = p [ e d [ u ] , s t [ v ] ] + p

如果一个结点扫了奇数次,那么我们加入ta的贡献
如果一个结点扫了偶数次,那么我们减去ta的贡献

如果我们强行加上修改,就像可修改的序列莫队那样搞就好了

struct node{
    int x,y,p,id;
}q[N];
int dfn[N],vis[N],cnt[N],tot=0,ans[N];

void update(int x) {       //传入结点编号 
    int co=c[x];
    if (vis[x]) {          //扫了偶数次 
        cnt[co]--;
        if (!cnt[co]) tot--;
    }
    else {                 //扫了奇数次 
        cnt[co]++;
        if (cnt[co]==1) tot++;
    }
    vis[x]^=1;
}

void solve() {
    int l=1,r=0;
    for (int i=1;i<=m;i++) {
        while (q[i].y<r) update(dfn[r]),r--;
        while (q[i].y>r) r++,update(dfn[r]);
        while (q[i].x<l) l--,update(dfn[l]);
        while (q[i].x>l) update(dfn[l]),l++;
        if (q[i].p) update(p);                    //处理lca 
        ans[q[i].id]=tot;
        if (q[i].p) update(p);
    }
}

可修改的莫队

对于可修改的序列莫队,本质上就像整体二分中记录一个修改指针

细节一

注意我们要先移动区间端点,再移动修改指针
这样能防止重复计算,并且保证我们的 c n t 数组中记录的一定是 [ L , R ] 的影响

在处理一个询问之前,暴力将指针移动到“能影响这个询问的修改都处理过了”的这么一个位置
在处理修改操作的时候,我们只有这个操作会影响该区间时才进行 u p d a t e
但是不敢怎么样我们都要 s w a p (之所以是 s w a p ,是为了我们修改完还要保证能够恢复

带修改莫队

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

const int N=1000010;
int n,m,cnt[N],cnt_a=0,cnt_q=0,ans[N];
int c[N],tot=0;
struct node{
    int x,y,block,num,id;
}q[N];
//x,y 区间左右端点 
//block 分块 
//num 在当前询问之前有多少个修改 s
struct point{
    int x,y;
}a[N];

int cmp(const node &a,const node &b) {
    //block=(x-1)/unit+1;
    if (a.block!=b.block) return a.block<b.block;
    else return a.y<b.y;
}

void update(int x,int z) {
    int co=c[x];
    if (z==1) {
        cnt[co]++;
        if (cnt[co]==1) tot++;
    }
    else {
        cnt[co]--;
        if (!cnt[co]) tot--;
    }
}

void change(int bh,int z,int l,int r) {
    if (a[bh].x>=l&&a[bh].x<=r) update(a[bh].x,-1);    //删除从前的影响 

    swap(c[a[bh].x],a[bh].y);

    if (a[bh].x>=l&&a[bh].x<=r) update(a[bh].x,1);     //添加影响 
}

void solve() {
    int L=1,R=0,now=0;
    for (int i=1;i<=cnt_q;i++) {
        while (R<q[i].y) R++,update(R,1);                   //移动区间端点 
        while (R>q[i].y) update(R,-1),R--;
        while (L<q[i].x) update(L,-1),L++;
        while (L>q[i].x) L--,update(L,1); 

        while (now<q[i].num) now++,change(now,1,L,R);       //移动修改标记 
        while (now>q[i].num) change(now,-1,L,R),now--;

        ans[q[i].id]=tot;                                   //维护答案 
    } 
}

int main()
{
    scanf("%d%d",&n,&m);
    int unit=sqrt(n);
    for (int i=1;i<=n;i++) scanf("%d",&c[i]);
    for (int i=1;i<=m;i++) {
        char s[10];
        int x,y;
        scanf("%s",s);
        scanf("%d%d",&x,&y);
        if (s[0]=='Q') {
            cnt_q++;
            q[cnt_q].x=x; q[cnt_q].y=y;
            q[cnt_q].num=cnt_a; q[cnt_q].block=(x-1)/unit+1;
            q[cnt_q].id=cnt_q;
        }
        else {
            cnt_a++;
            a[cnt_a].x=x; a[cnt_a].y=y;
        }
    }
    sort(q+1,q+1+cnt_q,cmp);
    solve();
    for (int i=1;i<=cnt_q;i++) printf("%d\n",ans[i]);
}
发布了941 篇原创文章 · 获赞 192 · 访问量 32万+

猜你喜欢

转载自blog.csdn.net/wu_tongtong/article/details/79798032