二分+三分+莫队全纪录

二分+三分

二分+三分讲解

二分是一种很强的方法,不要因为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;
树上莫队

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

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

  • lca(u,v)=p l c a ( u , v ) = p [ed[u],st[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);
    }
}
可修改的莫队

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

细节一

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

在处理一个询问之前,暴力将指针移动到“能影响这个询问的修改都处理过了”的这么一个位置
在处理修改操作的时候,我们只有这个操作会影响该区间时才进行 update u p d a t e
但是不敢怎么样我们都要 swap s w a p (之所以是 swap 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]);
}
重构思路: 首先,需要对现有的代码进行分析,找出其中存在的问题。然后,采用重构的原则与技巧,逐一解决这些问题,并增量地添加新功能。最后,以HTML格式输出订单。 1. 问题分析: 现有的代码存在以下问题: (1)`Movie`类中存在过多的if语句,导致代码难以维护和扩展。 (2)`Rental`类中计算租赁费用和积分的方法过于复杂,可读性差。 (3)`Customer`类中的`statement`方法过于复杂,可读性差,需要拆分为多个小方法。 2. 解决方案: (1)重构`Movie`类: 将`Movie`类中的if语句替换为多态,即在`Movie`类中添加一个抽象方法`getCharge`,然后在三个子类`RegularMovie`、`NewReleaseMovie`和`ChildrensMovie`中分别实现该方法,计算相应的租赁费用。 ``` abstract class Movie { //...省略其他代码... abstract double getCharge(int daysRented); } class RegularMovie extends Movie { //...省略其他代码... double getCharge(int daysRented) { double result = 2; if (daysRented > 2) result += (daysRented - 2) * 1.5; return result; } } class NewReleaseMovie extends Movie { //...省略其他代码... double getCharge(int daysRented) { return daysRented * 3; } } class ChildrensMovie extends Movie { //...省略其他代码... double getCharge(int daysRented) { double result = 1.5; if (daysRented > 3) result += (daysRented - 3) * 1.5; return result; } } ``` (2)重构`Rental`类: 将`Rental`类中计算租赁费用和积分的方法拆分为两个方法,分别计算租赁费用和积分。同时,将计算租赁费用和积分的方法移动到`Movie`类中,由多态来实现。 ``` class Rental { //...省略其他代码... double getCharge() { return _movie.getCharge(_daysRented); } int getFrequentRenterPoints() { // add bonus for a two day new release rental if ((_movie instanceof NewReleaseMovie) && _daysRented > 1) return 2; else return 1; } } //在Movie类中添加getCharge方法 abstract class Movie { //...省略其他代码... abstract double getCharge(int daysRented); } class RegularMovie extends Movie { //...省略其他代码... double getCharge(int daysRented) { double result = 2; if (daysRented > 2) result += (daysRented - 2) * 1.5; return result; } } class NewReleaseMovie extends Movie { //...省略其他代码... double getCharge(int daysRented) { return daysRented * 3; } } class ChildrensMovie extends Movie { //...省略其他代码... double getCharge(int daysRented) { double result = 1.5; if (daysRented > 3) result += (daysRented - 3) * 1.5; return result; } } ``` (3)重构`Customer`类: 将`Customer`类中的`statement`方法拆分为多个小方法,提高可读性。 ``` class Customer { //...省略其他代码... String statement() { Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); // show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n"; } // add footer lines result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n"; result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points"; return result; } private double getTotalCharge() { double result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); result += each.getCharge(); } return result; } private int getTotalFrequentRenterPoints() { int result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); result += each.getFrequentRenterPoints(); } return result; } } ``` (4)增加新的影片分类: 在`Movie`类中添加新的影片分类,如纪录片、综艺片等。具体实现方式与上述步骤类似,在`Movie`类中添加相应的子类,并重写`getCharge`方法。 ``` class DocumentaryMovie extends Movie { //...省略其他代码... double getCharge(int daysRented) { double result = 1.5; if (daysRented > 3) result += (daysRented - 3) * 1.5; return result; } } class VarietyShowMovie extends Movie { //...省略其他代码... double getCharge(int daysRented) { double result = 2; if (daysRented > 2) result += (daysRented - 2) * 1.5; return result; } } ``` (5)修改影片的租赁和积分标准: 在`Rental`类中修改`getFrequentRenterPoints`方法和`Movie`类中相应子类的`getCharge`方法。 ``` class Rental { //...省略其他代码... int getFrequentRenterPoints() { // add bonus for a two day new release rental if ((_movie instanceof NewReleaseMovie) && _daysRented > 1) return 3; //租赁新片大于1天,积分加3 else return 1; } } class RegularMovie extends Movie { //...省略其他代码... double getCharge(int daysRented) { double result = 1.5; //修改租赁价格 if (daysRented > 3) result += (daysRented - 3) * 1.5; return result; } } class ChildrensMovie extends Movie { //...省略其他代码... double getCharge(int daysRented) { double result = 1.5; //修改租赁价格 if (daysRented > 4) result += (daysRented - 4) * 1.5; return result; } } ``` (6)以HTML格式输出订单: 重构`Customer`类中的`statement`方法,将订单信息按照HTML格式输出。 ``` class Customer { //...省略其他代码... String statement() { Enumeration rentals = _rentals.elements(); String result = "<h1>Rentals for " + getName() + "</h1><br/>"; result += "<table border='1'><tr><th>Movie Title</th><th>Rental Days</th><th>Price</th></tr>"; //添加表头 while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); // show figures for this rental result += "<tr><td>" + each.getMovie().getTitle() + "</td><td>" + String.valueOf(each.getDaysRented()) + "</td><td>" + String.valueOf(each.getCharge()) + "</td></tr>"; } // add footer lines result += "</table><br/><h2>Amount owed is " + String.valueOf(getTotalCharge()) + "</h2>"; result += "<h2>You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points</h2>"; return result; } } ``` 3. 代码重构: 重构后的完整代码如下: ``` abstract class Movie { private String _title; public Movie(String title) { _title = title; } public String getTitle() { return _title; } abstract double getCharge(int daysRented); } class RegularMovie extends Movie { public RegularMovie(String title) { super(title); } double getCharge(int daysRented) { double result = 1.5; if (daysRented > 3) result += (daysRented - 3) * 1.5; return result; } } class NewReleaseMovie extends Movie { public NewReleaseMovie(String title) { super(title); } double getCharge(int daysRented) { return daysRented * 3; } } class ChildrensMovie extends Movie { public ChildrensMovie(String title) { super(title); } double getCharge(int daysRented) { double result = 1.5; if (daysRented > 4) result += (daysRented - 4) * 1.5; return result; } } class DocumentaryMovie extends Movie { public DocumentaryMovie(String title) { super(title); } double getCharge(int daysRented) { double result = 1.5; if (daysRented > 3) result += (daysRented - 3) * 1.5; return result; } } class VarietyShowMovie extends Movie { public VarietyShowMovie(String title) { super(title); } double getCharge(int daysRented) { double result = 2; if (daysRented > 2) result += (daysRented - 2) * 1.5; return result; } } class Rental { private Movie _movie; private int _daysRented; public Rental(Movie movie, int daysRented) { _movie = movie; _daysRented = daysRented; } public int getDaysRented() { return _daysRented; } public Movie getMovie() { return _movie; } double getCharge() { return _movie.getCharge(_daysRented); } int getFrequentRenterPoints() { // add bonus for a two day new release rental if ((_movie instanceof NewReleaseMovie) && _daysRented > 1) return 3; else return 1; } } class Customer { private String _name; private Vector _rentals = new Vector(); public Customer(String name) { _name = name; } public void addRental(Rental arg) { _rentals.addElement(arg); } public String getName() { return _name; } String statement() { Enumeration rentals = _rentals.elements(); String result = "<h1>Rentals for " + getName() + "</h1><br/>"; result += "<table border='1'><tr><th>Movie Title</th><th>Rental Days</th><th>Price</th></tr>"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); // show figures for this rental result += "<tr><td>" + each.getMovie().getTitle() + "</td><td>" + String.valueOf(each.getDaysRented()) + "</td><td>" + String.valueOf(each.getCharge()) + "</td></tr>"; } // add footer lines result += "</table><br/><h2>Amount owed is " + String.valueOf(getTotalCharge()) + "</h2>"; result += "<h2>You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points</h2>"; return result; } private double getTotalCharge() { double result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); result += each.getCharge(); } return result; } private int getTotalFrequentRenterPoints() { int result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); result += each.getFrequentRenterPoints(); } return result; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值