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

15 篇文章 0 订阅
7 篇文章 0 订阅

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(n a )

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

#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 long
const 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 5005
using 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这道例题。虽然联想到之前做过的题目是一个好习惯,但是不能拘泥于之前的方法。之前的方法难以实现,就大胆选择其他方法。同时注意时间安排,不能在一道题上耗得太久。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值