[VK Cup 2015 - Round 1]简要题解

在写这篇题解之前,首先orz学军两位屠场的神犇jcvb和jiry!!!!!!orzzzzzzzzzzzzz

B. Group Photo 2 (online mirror version)

题目大意

给你 n 个矩形,以及它们的 w()h() ,它们以它们的长为底边并排紧紧挨在一起,下边缘呈一条直线,你可以将其中不超过 [n2] 个矩形旋转 90 度,求最小的覆盖所有矩形的矩形面积是多少。

题解

因为题目范围非常小, wi,hi<=1000 ,因此最终的最小覆盖矩形的高也肯定不会超过 1000 ,因此可以直接爆枚最小覆盖矩形的高。然后求出当固定了这个覆盖矩形的高时,这个覆盖矩形的底边长最小是多少。显然将长比高大很多的矩形旋转90度是最优的,因此我们按照 wihi 降序排序,我们尽量选取矩形序列左边的矩形旋转90度。

虽然嘴巴讲讲很简单,但是还是有很多细节要注意。首先如果说当前爆枚的高不合法(高度太小了),要特判,另外如果说当前的高比某些矩形的高小,但是比它们的长大,那么首先就必须旋转这些矩形,并标记这些矩形已经旋转了90度,不能再选(旋转180度没有意义),我的标记方法就是将它们的 w 全部置为负无穷大。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1100

using namespace std;

struct Rectangle
{
    int w,h;
}rec1[MAXN],rec[MAXN];

bool cmp(Rectangle a,Rectangle b)
{
    return a.w-a.h>b.w-b.h;
}

int main()
{
    int n,minans=0x3f3f3f3f,len=0,maxh=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&rec1[i].w,&rec1[i].h),len+=rec1[i].w,maxh=max(maxh,rec1[i].h);
    minans=maxh*len;
    for(int H=1;H<1010;H++) //爆枚最小覆盖矩形的高
    {
        memcpy(rec,rec1,sizeof(rec1)); //!!!!!
        int cnt=0; //旋转次数
        int nowlen=len;
        bool flag=false;
        for(int i=1;i<=n;i++)
        {
            if(rec[i].w>H&&rec[i].h>H)
            {
                flag=true;
                break;
            }
            else if(rec[i].h>H) //!!!!!刚开始有矩形的高高于了H,那么需要旋转
            {
                cnt++;
                nowlen-=rec[i].w;
                nowlen+=rec[i].h;
                rec[i].w=-0x3f3f3f3f;
            }
        }
        sort(rec+1,rec+n+1,cmp); //!!!!!!
        if(flag||cnt*2>n) continue; //!!!!!!
        for(int i=1;(cnt+1)*2<=n&&i<=n;i++)
        {
            if(rec[i].w<=H&&rec[i].h<=rec[i].w)
            {
                flag=true;
                nowlen-=rec[i].w;
                nowlen+=rec[i].h;
                cnt++;
            }
        }
        //if(!flag) continue;
        minans=min(minans,nowlen*H);
    }
    printf("%d\n",minans);
    return 0;
}

C. Rooks and Rectangles

题目大意

一个 nm 的棋盘上有 t 个车,每个车可以攻击与它同行或同列且无障碍物阻挡的棋子。现有 q 个询问,每个询问查询棋盘上一个子矩阵中是否所有的棋子都能被车攻击。

题解

题目可以转化为,棋盘上某些行和列染了色,多次查询某个子矩阵中的所有格子是否都被染色了。

手玩一下可以发现,一个子矩阵中所有格子都被染色,要么是该矩阵所在的所有行上都有棋子,要么是所在的所有列上都有棋子。

因此我们可以把问题分成两半来看,一次我们只关心每次询问的子矩阵在列的方向上是否所有所在的列都染色了,一次我们只关心每次询问的子矩阵在列的方向上是否所有所在的行都染色了。

以前者为例,我们不妨设 x 轴方向为行, y 轴方向为列。假设当前查询的子矩阵左上角为 (x1,y1) ,右下角为 (x2,y2) 。那么显然所有 x 大于 x2 的车都是可以无视掉的。因此我们可以对所有询问按照 x2 升序排序,所有车按照 x 升序排序,离线处理所有询问,并维护一个线段树,保存区间最小值。线段树上的下标是 y 坐标。那么对于每个询问,我们先把所有 x<=x2 的车以 y 坐标为元素下标, x 坐标为元素的值加入线段树中,由于在上一次询问时已经加入了很多车,所以这一步加入的车的个数比较少。然后查询 [y1,y2] 区间内的最小值是否是 >=x1 ,是的话,那么只考虑列方向上的染色的话,这个子矩阵满足了要求。因为已经保证了线段树 [y1,y2] 范围内最大值不超过 x2 。若某个 y 坐标上没有一个车的 x 坐标在子矩阵范围内的话,那么肯定这些 x 都是小于 x1 的,若某个 y 坐标上没有车的话,那么 [y1,y2] 范围内的最小值是0

而后者的做法和前者基本一样,我就不赘述了

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 310000
#define lson ((o)<<1)
#define rson ((o)<<1|1)

using namespace std;

bool ans[MAXN]; //ans[i]=true表示yes

struct Query
{
    int x1,y1,x2,y2,id;
}querys[MAXN];

bool operator<(Query a,Query b)
{
    return a.x2<b.x2;
}

struct Point
{
    int x,y;
}points[MAXN];

bool operator<(Point a,Point b)
{
    return a.x<b.x;
}

int minv[MAXN<<2];

void pushup(int o)
{
    minv[o]=min(minv[lson],minv[rson]);
}

void update(int o,int L,int R,int x,int val) //单点修改
{
    if(L==R)
    {
        minv[o]=val;
        return;
    }
    int M=(L+R)>>1;
    if(x<=M) update(lson,L,M,x,val);
    else update(rson,M+1,R,x,val);
    pushup(o);
}

int query(int o,int L,int R,int ql,int qr)
{
    if(ql<=L&&R<=qr) //!!!!!!
        return minv[o];
    int M=(L+R)>>1;
    int ans=0x3f3f3f3f;
    if(ql<=M)
        ans=min(ans,query(lson,L,M,ql,qr));
    if(qr>M)
        ans=min(ans,query(rson,M+1,R,ql,qr));
    return ans;
}

int main()
{
    int n,m,t,q; //t个车,q个询问
    scanf("%d%d%d%d",&n,&m,&t,&q);
    for(int i=1;i<=t;i++)
        scanf("%d%d",&points[i].x,&points[i].y);
    for(int i=1;i<=q;i++)
        scanf("%d%d%d%d",&querys[i].x1,&querys[i].y1,&querys[i].x2,&querys[i].y2),querys[i].id=i;
    sort(points+1,points+t+1);
    sort(querys+1,querys+q+1);
    //只看列
    for(int i=1,j=1;i<=q;i++) //i维护的是当前询问编号,j维护的是当前点的编号
    {
        while(j<=t&&points[j].x<=querys[i].x2)
        {
            update(1,1,m,points[j].y,points[j].x);
            j++;
        }
        ans[querys[i].id]|=query(1,1,m,querys[i].y1,querys[i].y2)>=querys[i].x1;
    }
    //只看行
    swap(n,m);
    for(int i=1;i<=t;i++) swap(points[i].x,points[i].y);
    for(int i=1;i<=q;i++)
    {
        swap(querys[i].x1,querys[i].y1);
        swap(querys[i].x2,querys[i].y2);
    }
    sort(points+1,points+t+1);
    sort(querys+1,querys+q+1);
    memset(minv,0,sizeof(minv));
    for(int i=1,j=1;i<=q;i++) //i维护的是当前询问编号,j维护的是当前点的编号
    {
        while(j<=t&&points[j].x<=querys[i].x2)
        {
            update(1,1,m,points[j].y,points[j].x);
            j++;
        }
        ans[querys[i].id]|=query(1,1,m,querys[i].y1,querys[i].y2)>=querys[i].x1;
    }
    for(int i=1;i<=q;i++)
    {
        if(ans[i]) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

D. Social Network

题目大意

给你 n 次用户出现的时刻,不清楚每次出现的是哪个用户,但是知道在任意长为 T 的时间段(时刻1和时刻1+T不在同一长为T的时间段!)内,出现的不同的用户个数不超过 m ,并且有时候恰好为 m 。要你构造一组 n 行的用户数据,每行的数字对应于输入数据的这一行出现的用户的编号,使得满足上述题目条件,并且总共出现的不同用户个数最大。

题解

我参考了别人的一个非常不错的构造方法。
用一个双端队列维护直到当前时刻,长度为T的时间段内的每次用户出现的时间,并且时刻保证队列中没有相同的用户编号(这样队列大小就是这个T时间段内出现的不同用户个数)。每次将当前的用户出现的时刻信息放入队列中,并将队首一些已经过期的数据弹掉。
若当前队列中的信息量小于等于m,则当前的用户(就是队尾的数据)可以是新用户,否则当前的用户只能和上一次出现的用户一样,并把队尾的两个用户出现的时刻的数据合并在一起。
这样做显然能满足题目要求,而且总共出现的不同用户个数也显然是最大的。

代码

注意我上面打了黑体的边界条件,没注意到就会WA!

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>

#define MAXN 30000

using namespace std;

int occurtime[MAXN],q[MAXN],h=1,t=0;
int ans[MAXN]; //每个时刻的用户个数

int main()
{
    bool flag=false;
    int n,m,T,tot=0; //tot=用户总数
    scanf("%d%d%d",&n,&m,&T);
    for(int i=1;i<=n;i++)
    {
        int hour,min,sec;
        scanf("%d:%d:%d",&hour,&min,&sec);
        occurtime[i]=hour*3600+min*60+sec;
        q[++t]=i;
        while(h<=t&&occurtime[q[h]]<=occurtime[i]-T) h++; //!!!!!
        if(t-h+1==m) //达到了T时间内出现m个用户的要求
            flag=true;
        if(t-h+1<=m)
            ans[i]=++tot;
        else //这条信息和上条信息来自同一用户
        {
            ans[i]=ans[q[t-1]];
            q[t-1]=q[t]; //两个相邻的信息是同一用户发的,合并它们
            t--;
        }
    }
    if(!flag)
    {
        printf("No solution\n");
        return 0;
    }
    printf("%d\n",tot);
    for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
}

E. The Art of Dealing with ATM

题目大意

给你 n 种不同面值的钞票,以及一个参数 t ,共有 q 个询问,对于每一个询问,要你凑出 k 元钱,并且只能用最多2种钞票,用的钞票总张数不超过 t 。求最少要多少张钞票,若无法实现,输出-1。

题解

本次比赛中最水的题。。。
因为最多只能用两种钞票,因此只有两种情况:用了一种钞票或用了两种钞票,前者可以被包含在后者里头,因此我们就考虑要用两种钞票的情况就够了(第二种钞票张数可以为0)
用哈系表记录每个面额的纸币。首先爆枚第一种钞票是什么面额以及用了多少张,若只用这种钞票就已经凑齐了 k 元钱便不需继续枚举。然后爆枚第二种钞票用了多少张,在哈希表里查找算出的第二种钞票需要的面值是否存在即可。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <map>

#define MAXN 5100

using namespace std;

map<int,int>mp;

int a[MAXN];

int main()
{
    int n,maxn,k,ans;
    scanf("%d%d",&n,&maxn);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        mp[a[i]]=1;
    }
    int q;
    scanf("%d",&q);
    while(q--)
    {
        ans=0x3f3f3f3f;
        scanf("%d",&k);
        for(int i=1;i<=n;i++) //暴力枚举第一种纸币是哪一个
            for(int j=1;j<=maxn;j++) //用了j张
            {
                if(k==a[i]*j) ans=min(ans,j);
                if(k<a[i]*j) break;
                for(int t=1;t<=maxn-j;t++) //暴力枚举另外一张纸币用了多少张
                    if((k-a[i]*j)%t==0)
                        if(mp.count((k-a[i]*j)/t))
                            ans=min(ans,j+t);
            }
        if(ans==0x3f3f3f3f) printf("-1\n");
        else printf("%d\n",ans);
    }
    return 0;
}

你可能感兴趣的:([VK Cup 2015 - Round 1]简要题解)