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

T1 矩形覆盖

问题描述

数轴上有n个矩形排成一排,现在要求你用尽量少的矩形将它们恰好完全盖住,问,覆盖它们最少需要多个矩形。

输入格式

第一行,一个整数n。 接下来n行,每行两个整数x和y,从左往右依次给出了每个矩形的宽度和高度。

输出格式

一个整数表示所求答案


显然最多只用n个矩形即可。之后可以用贪心的思路,出现“山峰”状时,若“山峰”左右两边存在高度相等的矩形,那么可以用一个矩形覆盖它们,再用矩形覆盖比他们更高的。这样就比原来少用了一个。

“山峰”即一段高度单调递增的矩形接一段高度单调递减的矩形。用栈维护单调递增性即可。

矩形的高度与答案无关。

#include<stdio.h>
#include<stack>
using namespace std;
int N,Ans;

int main()
{
    int i,x,y;
    stack<int>S;

    scanf("%d",&N);
    for(i=1;i<=N;i++)
    {
        scanf("%d%d",&x,&y);
        while(S.size()&&y<S.top())S.pop(),Ans++;
        if(S.size()&&S.top()==y)continue;
        S.push(y);
    }

    while(S.size())S.pop(),Ans++;

    printf("%d",Ans);
}

T2 财务信息

问题描述

某公司的账本上记录了n个月(编号1到n)的营收情况,其中第i个月的收入为Ai元。当 Ai大于0时表示这个月盈利Ai 元,当 Ai小于0时表示这个月亏损Ai 元。所谓一段时间内的总收入,就是这段时间内每个月的收入额的总和。
作为老板的你懒得去查看所有财务细节,你要求会计向你做了m次财务汇报,每次财务汇报的形式为三个整数s、t 、v,表示第s到t个月这段时间的总收入为v元。
根据财务的汇报,你要快速做出判断,这些财务汇报中是否有虚假信息。

输入格式

第一行为一个正整数w,表示有w组数据,即w个账本,需要你判断。

对于每组数据:
第一行为两个正整数n和m,分别表示对应的账本记录了多少个月的收入情况以及进行了多少次汇报。
接下来的m行表示m次汇报的信息,每条信息占一行,有三个整数s,t和v,表示从第s个月到第t个月(包含第t个月)的总收入为v,这里s总是小于等于t。

输出格式

包含w行,每行是true或者false 其中第i行为false当且仅当第i个账本的财务信息是虚假的,否则为true。


虚假即汇报信息自相矛盾。假设当前信息与之前的信息相矛盾,那么当且仅当当前信息中区间总和与通过之前信息计算出的总和不一致时,满足条件。通过并查集维护前缀和可以实现。

主要思路是将s,t改为s-1和t(或:s和t+1),从而解决端点问题。在路径压缩的同时计算出节点与根节点之间的和。

注意一个小问题:如果是虚假的,可能在未读完所有数据时即可作出判断。此时不可以直接从当前循环break掉,否则会影响到下一组数据的读入。

另:由于数据规模并不大,一些图论算法也可AC。但是当数据规模较大时则只能用并查集,可参照其他并查集维护前缀和题目。

#include<stdio.h>
#include<cstring>
#define MAXN 105
#define ll long long

int W,M,N;
bool flag;

int fa[MAXN];ll V[MAXN];
int gf(int x)
{
    if(x==fa[x])return x;
    int anc=gf(fa[x]);
    V[x]+=V[fa[x]];//不是V[x]+=V[anc]!比赛时就错在这里
    return fa[x]=anc;
}

void init()
{
    memset(dis,0,sizeof(dis));
    for(int i=0;i<=N;i++)fa[i]=i;
    flag=true;
}

int main()
{
    int i,x,y,fx,fy;ll z;

    scanf("%d",&W);
    while(W--)
    {
        scanf("%d%d",&N,&M);
        init();

        for(i=1;i<=M;i++)
        {
            scanf("%d%d%lld",&x,&y,&z);
            x--;
            fx=gf(x);
            fy=gf(y);
            if(fx==fy)
            {
                if(V[y]-V[x]!=z)flag=false;
            }
            else
            {
                fa[fy]=fx;
                V[fy]=V[x]+z-V[y];//画图可轻松理解
            }
        }
        if(!flag)puts("false");
        else puts("true");
    }
}

T3 清理花瓶

问题描述

Nicole是大家心中的女神,她每天都会收到很多束男生们送的鲜花。她有N个花瓶,编号0到N-1.
每天她都会试着把收到的花束放到花瓶里,一个花瓶只能装一束花。她总是随机地选择一个花瓶A,如果该花瓶是空的,她就放一束花在里面,否则她会跳过这个花瓶。接着她会试着往A+1,A+2,…,N-1号花瓶里面放花,直到没有花了或者是第N-1号花瓶都被讨论了。然后她会扔掉剩下的花。
有时Nicole也会去清理花瓶,因为花瓶太多了,所以她每次都随机选择一段花瓶清理。比如编号A到编号B这一段花瓶,她会把里面的花全都扔掉。

输入格式

第一行,两个整数N和M,表示花瓶的个数和操作的次数
接下来M行,每行三个整数K,A,B。
若K=1,表示今天Nicole收到了B束花,她打算从第A号花瓶开始放花。
若K=2,表示今天Nicole要清理花瓶,她打算清理掉从第A号到第B号花瓶里的花(A <= B)。

输出格式

对于每次操作,输出一行结果:
若K=1,输出Nicole放花的起止花瓶的编号。若一束花也放不进去,输出 “Can not put any one.”
若k=2,输出Nicole清理掉的花的数目。


显然是线段树。

2号操作是区间修改的基本操作。对于1号操作:
首先求出A~N-1区间中含有空花瓶的个数。若区间中不含空花瓶,那么输出题目中要求的字符串;否则选择空花瓶个数与B中较小的那个从尽量左的位置开始放。

难点在于找到左右端点。若找到了就可以直接上区间修改了。这里标程用的是二分查找。但比赛时一开始把题目条件理解错了,所以写得有些糟糕,复杂度为O(NlogNlogN),AC还是没问题。

posL,posR分别记录当前节点最左边及右边的空花瓶的编号,代码中把0~N-1转换为了1~N。

#include<stdio.h>
#define MAXN 50005
#define MAXT 400005
#define Max(x,y) ((x>y)?(x):(y))
#define Min(x,y) ((x<y)?(x):(y))

inline int _R()
{
    char s=getchar();int v=0;
    while(s>57||s<48)s=getchar();
    for(;s>47&&s<58;s=getchar())v=v*10+s-48;
    return v;
}

int a[MAXT],b[MAXT],sum[MAXT],lazy[MAXT],posL[MAXT],posR[MAXT];
int N,M,AnsL,AnsR;

void Putdown(int p)
{
    int ls=p<<1,rs=p<<1|1,tmp=lazy[p],op;
    lazy[p]=0;
    lazy[ls]=lazy[rs]=tmp;
    if(tmp==1)
    {
        op=0;
        posL[ls]=posR[ls]=1e9;
        posL[rs]=posR[rs]=-1;
    }
    else
    {
        op=1;
        posL[ls]=a[ls];posR[ls]=b[ls];
        posL[rs]=a[rs];posR[rs]=b[rs];
    }

    sum[ls]=op*(b[ls]-a[ls]+1);
    sum[rs]=op*(b[rs]-a[rs]+1);
}

void Update(int p)
{
    int ls=p<<1,rs=p<<1|1;
    sum[p]=sum[ls]+sum[rs];
    posL[p]=posL[ls];posR[p]=posR[rs];
}

void Build(int p,int l,int r)
{
    a[p]=l;b[p]=r;
    if(l==r){sum[p]=1;posL[p]=posR[p]=l;return;}
    int mid=l+r>>1;
    Build(p<<1,l,mid);
    Build(p<<1|1,mid+1,r);
    Update(p);
}

int GetSum(int p,int l,int r)
{
    if(a[p]>r||b[p]<l)return 0;
    if(lazy[p])Putdown(p);
    if(a[p]>=l&&b[p]<=r)return sum[p];
    return GetSum(p<<1,l,r)+GetSum(p<<1|1,l,r);
}

void Clear(int p,int l,int r)
{
    if(a[p]>r||b[p]<l)return;
    if(lazy[p])Putdown(p);
    if(a[p]>=l&&b[p]<=r)
    {
        lazy[p]=2;
        sum[p]=b[p]-a[p]+1;
        posL[p]=a[p];
        posR[p]=b[p];
        return;
    }
    Clear(p<<1,l,r);
    Clear(p<<1|1,l,r);
    Update(p);
}

void Add(int p,int l,int r,int k)
{
    if(a[p]>r||b[p]<l||k==0||sum[p]<k)return;
    if(lazy[p])Putdown(p);

    if(a[p]>=l&&b[p]<=r&&sum[p]==b[p]-a[p]+1&&k==sum[p])
    {
        AnsL=Min(AnsL,posL[p]);
        AnsR=Max(AnsR,posR[p]);
        sum[p]=0;
        posL[p]=1e9;
        posR[p]=-1;
        lazy[p]=1;
        return;
    }

    int ls=p<<1,rs=p<<1|1,tmp,aa,bb;
    aa=Max(a[ls],l);
    bb=Min(b[ls],r);
    tmp=GetSum(1,aa,bb);
    if(k>tmp)
    {
        Add(p<<1,l,r,tmp);
        Add(p<<1|1,l,r,k-tmp);
    }
    else Add(p<<1,l,r,k);
    Update(p);
}

int main()
{
    int i,K,A,B,Sum,tmp;

    N=_R();M=_R();

    Build(1,1,N);


    for(i=1;i<=M;i++)
    {
        K=_R();A=_R();B=_R();
        A++;B;

        if(K==1)
        {
            Sum=GetSum(1,A,N);AnsL=N;AnsR=1;
            if(Sum==0)puts("Can not put any one.");
            else
            {
                tmp=B>Sum?Sum:B;
                Add(1,A,N,tmp);
                AnsL--;AnsR--;
                if(AnsL>AnsR)puts("Can not put any one.");
                else printf("%d %d\n",AnsL,AnsR);
            }
        }
        else 
        {
            B++;
            Sum=GetSum(1,A,B);
            printf("%d\n",B-A+1-Sum);
            Clear(1,A,B);
        }
    }
}

总结:

本次比赛主要以考查数据结构的应用为主。T1是单调栈水题,T2是带权并查集维护前缀和,T3是线段树内的区间修改与二分查找。
T1、T3做得还算不错,T2首先想到了并查集,但是由于之前没有写过此类题,出于保险很快写了一个图论算法。之后想到了并查集做法,却因为一个小错误把100分写法搞成了10分,实在是很可惜。对于学过的知识点,应当做到熟悉与全面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值