基础算法1

离散化

就是把无限空间(在OI中就是很大的范围)里面的信息,映射到一个较小的空间里面

有时候需要保证仍然保留了一些信息,比如元素之间的大小关系,比如相邻两个元素的差(去重w)

一个对闭合区间离散化的小技巧

有若干个区间$[L_i,R_i] $,把他们离散化成若干个区间:

如何划分?

集合Sp表示覆盖这个点p的区间编号

将数轴上的点划分成n个区间,每个区间的点等价(区间内的点被覆盖的区间相同,也就是集合Sp相同);

举个例子:

[1,3]------①

[2,5]------②

那么划分为三个区间:

[1,1]={①};

[2,3]={①,②};

[4,5]={②}

如何实现?

1.将所有的$L_i,R_i+1 $都拿出来排序(排序时不考虑L与R的区别);

我们设排序去重后的数组为V;

那么相邻两个元素可以得到一个区间$[V_i,V_{i+1}-1] $

而得到的每一个区间,也就像我们上面↑举的例子所划分的三个区间一样的;

如何去重:

sort(V + 1, V + 1 + N);
M = unique(V + 1, V + 1 + N) - (V + 1);

如何应用?

举个栗子:

假设读入区间为$[1,10^7],[10^5+1,10^9] $

那么我们划分的区间就是:

\([1,10^5]\) ---------①

\([10^5+1,10^7]\)-----------②

\([10^7+1,10^9]\)-----------③

这样,对于读入的两个区间,就可以映射为:

$[1,10^7]=>[①,②] $

$[10^5+1,10^9]=>[②,③] $

前缀和和差分

什么是前缀和?
对于一个数组A,记录S i = A[1]+ A[2]+ ⋯ +A[i]
显然S[i]= S[i−1]+A[i],所以S数组可以线性递推出来
那么A[l]+ A[l+1]+ ⋯ +A[r]= S[r]− S[l−1]
这样就可以把,求一个数组中一段区间的和这个O(r − l)的事情变
成O(1)的啦
实际上,只要有可加可减性的信息都可以这么搞
求出前缀积、前缀异或和等等

什么是差分?
对一个数组A进行差分,形式化而言就是:D[i]= A[i]−A[i−1]
对原序列A的区间[L, R]进行+1等价于D[L]+= 1, D[R+1]−= 1
那么可以直接维护D
用D还原A也是轻松的,差分的逆运算是前缀和,直接对D做前缀和即可还原A

洛谷P3406

据说是一道差分模板题,我们可以记录每一条铁路经过的次数,如果O(n^2)的去加,显然会超时(n超大),那么我们可以考虑差分数组。

因为第i段铁路表示的是第i个城市~第i+1个城市,所以对于一段x=>y,我们只需要在两个中较小的对应的差分数组+1,较大的-1,然后对每一段铁路,贪心的比较是\(c_i+b_i*经过次数\)与$a_i*经过次数 $的大小关系,取较小一个加进ans里;

这样处理就好了√;

#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

ll n,m;
ll d[100010];
ll a,b,c;

int main(){
    n=read();
    m=read();
    ll p,nxt;
    ll z,y;
    for(int i=1;i<=m;i++) {
        p=read();
        if(i==1) {
            nxt=p;
            continue;
        }
        y=min(p,nxt);
        z=max(p,nxt);
        d[y]++;
        d[z]--;
        nxt=p;  
    }
    ll ans=0;
    for(int i=1,x=0;i<n;i++) {
        a=read();
        b=read();
        c=read();
        x+=d[i];
        if(c+b*x<a*x) ans+=c+b*x;
        else ans+=a*x;
    }
    
    printf("%lld",ans);
    return 0;
}

洛谷P1115

之前做这道题好像是用dp做的,转移方程大概是dp[i]=max{dp[i-1]+a[i],a[i]};

现在尝试用前缀和做,也就是找一对l,r,使得sum[r]-sum[l]最大,从左往右维护sum[i]的最小值,然后与sum[i]相减(注意不可以sum[i]-sum[i]),求最大(好乱)

#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

ll n,ans;
ll sum[200010];

int main(){
    n=read();
    ll minn=21474836470000;
    for(int i=1,x;i<=n;i++) {
        x=read();
        sum[i]=sum[i-1]+x;
    }
    ans=sum[1];
    minn=sum[1];
    ll y;
    for(int i=2;i<=n;i++) {
        y=max(sum[i],sum[i]-minn);
        if(sum[i]<minn) minn=sum[i];
        ans=max(ans,y);
        
    }
    printf("%lld",ans);
    return 0;
}

洛谷P3397

二维前缀和与二维差分:

\(s[x][y]=s[x][y-1]+s[x-1][y]-s[x-1][y-1]+a[x][y]\)

二维差分:

对于(x1,y1)~(x2,y2) 的区间A+1;

等价于:

差分数组D:\(D[x1][y2+1]-=1 \ \ D[x2+1][y1]-=1 \ \ D[x2+1][y2+1]+=1 \ \ D[x1][y1]+=1;\)

for (x = 1 ~ N)
    for (y = 1 ~ M)
        S[x][y] = S[x - 1][y] + S[x][y - 1] - S[x - 1][y - 1] + A[x][y];
//表示不懂下面在干什么:

for (x = 1 ~ N)//对每一行做一个一维前缀和
    for (y = 1 ~ M)
        S[x][y] = S[x][y - 1] + A[x][y];
for (y = 1 ~ M)//对列做前缀和
    for (x = 1 ~ N)
        S[x][y] += S[x - 1][y];

维护二维数组D,然后最后做二维前缀和:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

int n,m;
int D[1010][1010],s[1010][1010];

int main(){
    n=read();
    m=read();
    int x1,y1,x2,y2;
    for(int i=1;i<=m;i++) {
        x1=read();y1=read();
        x2=read();y2=read();
        D[x1][y1]+=1;
        D[x2+1][y2+1]+=1;
        D[x1][y2+1]-=1;
        D[x2+1][y1]-=1;
    }
    for(int x=1;x<=n;x++) 
        for(int y=1;y<=n;y++) 
            s[x][y]=s[x-1][y]+s[x][y-1]-s[x-1][y-1]+D[x][y];
    
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=n;j++) 
            printf("%d ",s[i][j]);
        puts("");
    }
    return 0;
}

树上差分:

对边差分:

(u,v)全部加上w,对于差分数组D就是:

D[u]+=w;D[v]+=w;D[lca(u,v)]-=2*w;

用子树中差分数组的和来还原信息:即将子树中所有的D[i] i∈son(r) 相加;

每个点的信息记录的是其到父亲的边的信息

对点差分:

(u,v)全部加上w,对于差分数组D就是:

D[u]+=w;D[v]+=w;D[lca(u,v)]-=w;Father~lca~-=w;

感性李姐

洛谷P3258

利用上面对点差分的思想,进行加减操作,注意因为下一段路径的起点是上一段路径的终点,所以除a[1]外,其他经过的点的值要顺次减一。求和是做子树和不是树上前缀和。

#include<bits/stdc++.h>

using namespace std;

inline int read() {
    int ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-' ) ans=-ans;
    return ans;
}
const int mxn=300010;
int n,ecnt;
int a[mxn],head[mxn],d[mxn];
int fa[mxn][30],dep[mxn];
bool vis[mxn];
struct node {
    int to,nxt;
}e[mxn<<1];

void add(int u,int v) {
    ++ecnt;
    e[ecnt].to=v;
    e[ecnt].nxt=head[u];
    head[u]=ecnt;
    ++ecnt;
    e[ecnt].to=u;
    e[ecnt].nxt=head[v];
    head[v]=ecnt;
}

void dfs(int u,int f) {
    vis[u]=1;
    for(int i=head[u],v;i;i=e[i].nxt) {
        v=e[i].to;
        if(vis[v]) continue;
        dep[v]=dep[u]+1;
        fa[v][0]=u;
        dfs(v,u);
    }
}

void fill () {
    for(int i=1;i<=29;i++) 
        for(int j=1;j<=n;j++) 
            fa[j][i]=fa[fa[j][i-1]][i-1];
}


int lca(int x,int y) {
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=29;i>=0;i--) 
        if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
        
    if(x==y) return x;
    for(int i=29;i>=0;i--) {
        if(fa[x][i]!=fa[y][i]) {
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}

void sum(int u,int f) {
    for(int i=head[u],v;i;i=e[i].nxt) {
        v=e[i].to;
        if(v==f) continue;
        sum(v,u);
        d[u]+=d[v];
    }
}

int main(){
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1,x,y;i<n;i++) x=read(),y=read(),add(x,y); 
    dep[1]=1;
    dfs(1,0);
    fill();
    for(int i=1;i<n;i++) {
        int L=lca(a[i],a[i+1]);
        d[a[i]]++;
        d[a[i+1]]++;
        d[L]--;
        d[fa[L][0]]--;
    }
    sum(1,0);
    for(int i=2;i<=n;i++) d[a[i]]--;
    for(int i=1;i<=n;i++) printf("%d\n",d[i]);
    return 0;
}

树上前缀和

定义为一个点到根路径的点权和

差分和数据结构的结合

对于一个支持单点修改、区间求和的数据结构,如果使用差分,就可以支持区间加法、单点查询(维护差分数组)
甚至可以支持区间加法、区间求和
一个经典的例子就是用树状数组来完成这些事情
用DFS序还可以把放到树上,区间变成子树

贪心

大胆猜想,无需证明

luoguP1376

第i周生产需要代价\(x=min\{c[i]*y[i],c[j]*y[i]+s*y[i]*(i-j)\} \ j\in [1,i-1]\)

那么对于第i周,我们贪心的选择最小的即可:

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline ll read() {
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-' ) ans=-ans;
    return ans;
}

ll n,s;
ll c[10010],y[10010];

int main(){
    n=read();s=read();
    for(int i=1;i<=n;i++) {
        c[i]=read();
        y[i]=read();
    }
    ll minn;
    ll ans=0;
    for(int i=1;i<=n;i++) {
        minn=y[i]*c[i];
        for(int j=1;j<i;j++) 
            minn=min(minn,c[j]*y[i]+y[i]*s*(i-j));
        ans+=minn;
    }
    printf("%lld",ans);
    return 0;
}

排序不等式:

正序和不小于乱序和,乱序和不小于逆序和

A[i] B[i]

均从小到大排序,则:

\(\sum A[i]*B[j]=A数组从小到大排序*B数组从小到大排序(max) > \sum A[i]*B[j]A数组从大到小排序*B数组从小到大排序\)

洛谷P1842

只想大胆猜想,不想小心证明:

把W+S较小的放在上面

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline ll read() {
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-' ) ans=-ans;
    return ans;
}
const int mxn=50010;

ll n;
struct node {
    ll s,w;
}cow[mxn];

bool cmp(node a,node b) {
    return a.s+a.w<b.s+b.w;
}

int main(){
    n=read();
    for(int i=1;i<=n;i++) {
        cow[i].w=read();
        cow[i].s=read();    
    }
    sort(cow+1,cow+n+1,cmp);
    ll ans=-2147483647;
    ll sum=0;
    for(int i=1;i<=n;i++) {
        ans=max(ans,sum-cow[i].s);
        sum+=cow[i].w;
    }
    printf("%lld",ans);
    return 0;
}

p1223√

p1012√

p1080 高精×

一个有点厉害的题

有一个初始为0的数X
有若干个操作,第i个操作是给X先加上Ai再减去Bi,这两个都是非负的数
找到一个操作的排列,使得进行完操作之后,X最大的时候最小

考虑一个完整的不被分割的序列,可以用一个二元组(sum, max)来表示。 sum是序列中的和, max是最大前缀和

那么每个操作单独看成的序列,就是\((A_i − B_i, A_i)\)

两个序列分别是\((suma, maxa)\)\((sumb, maxb)\),如果把第二个序列接到第一个序列后面,新序列就是\((suma +sumb, max(maxa, suma + maxb))\)

按照前面的贪心方法,比较两个元素排序的关键字,就是两种方法新得到的max比大小

相等的时候显然把sum较小的放到前面对全局的影响更优

这个题还可以放到树上 ,具体而言就是对于操作的先后顺序有一些限制,而且限制关系形成了一颗树

哈夫曼编码
Kruskal求最小生成树
Dijkstra求单源最短路

调整法贪心

p1230

(没删调试信息wa好久)

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline ll read() {
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch>'9'||ch<'0') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-' ) ans=-ans;
    return ans;
}
const int mxn=510;
bool vis[mxn];
int m,n;
struct node {
    int ti,mon;
}a[mxn];

bool cmp(node x,node y) {
    return x.mon>y.mon;
}

int main(){
    m=read();
    n=read();
    for(int i=1;i<=n;i++) a[i].ti=read();
    for(int i=1;i<=n;i++) a[i].mon=read();

    sort(a+1,a+n+1,cmp);
    bool bj=0;
    int ans=0;
    for(int i=1;i<=n;i++) {
        bj=0;
        for(int j=a[i].ti;j;j--) {
            if(!vis[j]){
                bj=1;
                vis[j]=1;
                break;
            }
        }
        if(!bj) {
            for(int j=n;j;j--) {
                if(!vis[j]) {
                    vis[j]=1;
                    break;
                }
            }
        
            ans+=a[i].mon;
        }
    }
    int k=m-ans;
    printf("%d\n",k);
}

p4053(gugugu)

考虑按照截止时间排序,能修就修

一个关于匹配的模型:

max>sum-max 无法匹配

否则一定存在构造:

按照a1……an依次排开:

a11,a12,a13,……,b11,b12………………n11,n12,……

i与i+sum/2匹配

猜你喜欢

转载自www.cnblogs.com/zhuier-xquan/p/11622855.html