2017暑假第二阶段第七场 总结

来源:互联网 发布:windows无法连接到cmcc 编辑:程序博客网 时间:2024/06/08 07:51

T1 最大子段和

问题描述

给出一个首尾相连的循环序列,从中找出连续的一段,使得该段中的数和最大

输入格式

第一行一个整数 n,表示有 n 个数。( 1<=n<=100000)
第二行有 n 个整数,每个数的绝对值不超过 100000.

输出格式

一个整数,表示所求结果

样例输入

4
2 -4 1 4

样例输出

7


正解是用的单调队列优化DP。

由于是个环,首先把数组复制成两倍,预处理出前缀和sum。显然,当以i结尾的连续最大和f[i]满足:
f[i]=sum[i]min(sum[j]),inj<i
那么维护一个单调递增的单调队列即可。

#include<stdio.h>#include<deque>#define ll long long#define Max(x,y) ((x>y)?(x):(y))using namespace std;int N;ll sum[200005],a[200005],Ans=-(1LL<<60);int main(){    int i;    ll x;    deque<int>Q;    scanf("%d",&N);    for(i=1;i<=N;i++)scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];    for(i=1;i<=N;i++)sum[i+N]=sum[i+N-1]+a[i];    Q.push_back(0);    for(i=1;i<=N*2;i++)    {        if(Q.size()&&(i-Q.front()>N))Q.pop_front();        if(Q.size())Ans=Max(Ans,(sum[i]-sum[Q.front()]));        while(Q.size()&&sum[i]<=sum[Q.back()])Q.pop_back();        Q.push_back(i);    }    printf("%lld",Ans);}

当然也可以用优先队列或线段树维护,甚至可以直接搞成一道线段树求区间连续最大和且不带修改的题,但是都不如单调队列优秀。

下面是优先队列的做法:

#include<stdio.h>#include<queue>#define ll long long#define Max(x,y) ((x>y)?(x):(y))using namespace std;priority_queue<pair<ll,int> >Q;int N;ll sum[200005],a[200005],Ans=-(1LL<<60),tmp;int main(){    int i;    scanf("%d",&N);    for(i=1;i<=N;i++)scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];    for(i=1;i<=N;i++)sum[i+N]=sum[i+N-1]+a[i];    Q.push(make_pair(0,0));    for(i=1;i<=N*2;i++)    {        while(Q.size()&&i-(Q.top().second)>N)Q.pop();        if(Q.size())Ans=Max(Ans,(sum[i]+Q.top().first));        Q.push(make_pair(-sum[i],i));//小根堆    }    printf("%lld",Ans);}

T2 统计

问题描述

现在有一个数组 a,数组中有 n 个元素。定义一个函数 f(l,r)表示 i(l<=i<=r)的 个数,其中 i 符合条件:不存在 j (l<=j<=r 且 j≠i)满足 ai mod aj = 0 求

ni=1nj=1f(i,j)mod109+7

即所有区间中包含的符合条件的 i 的个数。

输入格式

第一行一个整数 n(n<=100000)
第二行有 n 个数,表示数组中的元素 ai,0 < ai <= 10000

输出格式

表示所求的结果。注意要取模。


很明显,对于任意一个数x,若它的左边存在一个数y,使得y|x,那么左端点在y的左边且含有x的区间中,x都不符合条件;右边同理。所以找出离每个数左右分别最近的因数位置,那么区间端点在这两数之间(不含)的区间就是这个数符合条件的区间。

可以预处理出1~10000的每个数的因数,也可以直接分解。时间复杂度O(na)

找出离这个数最近的因数的位置,可以在遍历时同时处理。

#include<stdio.h>#include<cstring>#define Max(x,y) ((x>y)?(x):(y))#define Min(x,y) ((x<y)?(x):(y))#define MAXN 100005#define ll long longconst ll mod=1e9+7;int N,A[MAXN],L[MAXN],R[MAXN],lastl[10005],lastr[10005];ll Ans;// lastl[i]和lastr[i]分别记录数i上一次出现的位置int main(){    int i,j;    scanf("%d",&N);    for(i=1;i<=N;i++)scanf("%d",&A[i]);    memset(lastr,60,sizeof(lastr));    for(i=1;i<=N;i++)    {        for(j=1;j*j<=A[i];j++)        {            if(A[i]%j!=0)continue;            L[i]=Max(L[i],lastl[j]);            L[i]=Max(L[i],lastl[A[i]/j]);        }        lastl[A[i]]=i;    }    for(i=1;i<=N;i++)R[i]=N+1;    for(i=N;i;i--)    {        for(j=1;j*j<=A[i];j++)        {            if(A[i]%j!=0)continue;            R[i]=Min(R[i],lastr[j]);            R[i]=Min(R[i],lastr[A[i]/j]);        }        lastr[A[i]]=i;    }    for(i=1;i<=N;i++)Ans=(Ans+ZVCXFGbn (i-L[i])*(R[i]-i)%mod)%mod;    printf("%lld",Ans);}

T3 最小差值生成树

问题描述

给定一个无向图,求它的一棵生成树,使得生成树中的最大边权与最小边权 的差最小,输出其最小差值。

输入格式

第一行两个整数 n(2接下来 m 行,第 i+1 行包含三个整数 Xi(0保证图是连通的,两个点之间最多只有一条边。

输出格式

包含一行,表示最小差值生成树的最大边与最小边的差值。

样例输入

3 3
1 2 10
1 3 20
2 3 30

样例输出

10


根据最小生成树的性质,当最短边的长度唯一确定时,最长边的长度也是唯一确定的。而且如果去掉当前的最短边再做一次最小生成树时,最长边的长度也会增长。而如果不做最小生成树,最长边的长度还会增大。因此可能的答案必须是一些最小生成树的最大边与最小边权值之差。

因此先把边按照权值从小到大排序后,枚举最小边的长度用Kruscal算出最小生成树,同时更新最优解即可。

可以优化之处在于,如果从小到大枚举最小边长度,若某一刻不能做出最小生成树,那么之后也一定不会做出最小生成树,也就是说这时就可以终止循环并输出答案了。

#include<stdio.h>#include<algorithm>#define Min(x,y) ((x<y)?(x):(y))#define MAXN 205#define MAXM 5005using namespace std;int N,M,Ans=1e9;inline int _R(){    char s=getchar();int v=0,sign=0;    while((s!='-')&&(s>57||s<48))s=getchar();    if(s=='-')sign=1,s=getchar();    for(;s>47&&s<58;s=getchar())v=v*10+s-48;    if(sign)v=-v;    return v;}struct node{int a,b,len;}edge[MAXM];bool operator<(node x,node y){return x.len<y.len;}int fa[MAXN];int gf(int x){    if(fa[x]!=x)fa[x]=gf(fa[x]);    return fa[x];}bool Kruscal(int l){    int i,cnt=0,x,y,fx,fy;    for(i=l;i<=M&&cnt<N-1;i++)    {        x=edge[i].a;y=edge[i].b;        fx=gf(x);fy=gf(y);        if(fx==fy)continue;        fa[fx]=fy;        cnt++;    }    if(cnt!=N-1)return false;    Ans=Min(Ans,(edge[i-1].len-edge[l].len));    return true;}int main(){    int i,j;    N=_R();M=_R();    for(i=1;i<=M;i++)edge[i].a=_R(),edge[i].b=_R(),edge[i].len=_R();    sort(edge+1,edge+M+1);    for(i=1;M-i+1>=N-1;i++)    {        for(j=1;j<=N;j++)fa[j]=j;        if(!Kruscal(i))break;//上面所说的优化之处    }    printf("%d",Ans);}

总结

T1如果想到了单调队列,那么是一道比较简单的DP;T2想到了上文所说的性质之后,如果有难点,只在lastl,lastr数组上;T3是最小生成树例题。

这次比赛打得不好,主要是做T1时思想江化,一直想用不是环状的O(n)做法进行改进,根本就没考虑用前缀和实现的DP,所以也没有想到单调队列,想用线段树写连续区间最大和也写炸了,导致没时间做T2,只过了T3这道例题。虽然联想到之前做过的题目是一个好习惯,但是不能拘泥于之前的方法。之前的方法难以实现,就大胆选择其他方法。同时注意时间安排,不能在一道题上耗得太久。

原创粉丝点击