Codeforces Round #330 (Div. 2)(A,B,C,D,E)

Codeforces Round #330 (Div. 2)(A,B,C,D,E)

tags: Codeforces


寒假集训开始了,于是花了一天时间搞定了这场CF作为练手(虽然无数次偷看数据和题解,但最终还是搞定了5题

A.Vitaly and Night

题意

每两个数字代表一个房间中的两盏灯,灯亮为1,灯灭为0。只要有一盏灯亮着,就认为房间的主人醒着。问有多少房间里的人还没睡。

解析

两个一组,统计一下即可得出答案。

代码

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

int main()
{
    int n, m;
    int cnt = 0;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m ; ++j)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            if (a || b)
                cnt++;
        }
    }
    printf("%d\n", cnt);
    return 0;
}

B.Pasha and Phone

这道题坑得我好惨,题意坑完数据坑,整整一下午时间都花在了这上面

题意

将长为n的电话号码分为长k的子串,对于每个子串s[i],给出对应的a[i]和b[i] (1ai<10,0bi9) 。求有多少个电话号码满足:

  • 对于任意子串s[i],将其转化为对应的十进制数d,满足 dmodai=0
  • 所有子串s[i]不由b[i]开始(注意,“0001”由‘0’开始,而不是‘1’)

解析

实际上就是一个类似容斥的题,对于每个子串s[i],从所有满足情况的a[i]的倍数中减去由b[i]打头的数目,得到c[i],最后将所有c[i]相乘即得到答案。

最开始的时候我遍历所有a[i]的倍数进行统计,但最终超时。

实际上,我们可以通过(MAXX - 1) / a[i] + 1直接计算出[0,MAXX-1]范围内a[i]倍数的个数(包括0),记为total,此处MAXX是10^k。
同理,使用((b[i] + 1)*(MAXX / 10) - 1) / a[i] + 1计算出[0,b[i]99..9]范围内的个数high,使用(b[i] * (MAXX / 10) - 1) / a[i] + 1计算出[0,(b[i]-1)99..9]范围内的个数low。
则,以b[i]开头的数的个数为(high-low),而所求的答案即为total-(high-low)

需要注意的是,当b[i]为0时,不存在low,因此此时答案为total-high

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>
#include <algorithm>

using namespace std;

#define MAXN    100000+100
#define MOD     1000000007

long long a[MAXN], b[MAXN],c[MAXN];

int main()
{
    long long n, k;
    scanf("%lld%lld",&n,&k);
    long long cnt = n / k;
    for (int i = 0; i < cnt; i++)
        scanf("%lld",&a[i]);
    for (int i = 0; i < cnt; i++)
        scanf("%lld", &b[i]);
    long long ans = 1;
    long long MAXX = 1;
    long long len = k;
    while (len--)
        MAXX *= 10;
    for (int i = 0; i < cnt; i++)
    {
        long long total = (MAXX - 1) / a[i] + 1;                    //[0,MAXX-1]
        long long high = ((b[i] + 1)*(MAXX / 10) - 1) / a[i] + 1;   //[0,b[i]99..9]
        long long low = (b[i] * (MAXX / 10) - 1) / a[i] + 1;        //[0,(b[i]-1)99..9]
        if (b[i] == 0)
            c[i] = total - high;
        else
            c[i] = total - (high - low);
        if (c[i] == 0)
        {
            printf("0\n");
            return 0;
        }
        ans *= c[i];
        ans %= MOD;
    }
    printf("%lld\n",ans);

    return 0;
}
/*
10 1
3 1 1 4 8 7 5 6 4 1
0 0 0 5 5 6 8 8 4 0
*/

C.Warrior and Archer

题意

战士和弓箭手PK,战士想要拉进距离,而弓箭手想要拉开距离。现在可以选择的位置一共有n个,分布在x轴上。两人轮流ban地点(一次只能ban一个,战士先ban,被ban的地点两个人都不能使用也不能再ban),直到最后只剩下两个位置,两个人各占一个开始PK。两个人都足够聪明,求最终两点间的距离。

解析

经过观察可以发现,战士ban的地点应该都在两端,而弓箭手ban的地点应该都在中间。

假设每一轮后剩下的点是集合S,即最终区间可能的值记为[Xi,Xj],Xi,Xj是点集S中的点,且Xi < Xj。当集合S中只剩下两个点时,区间[Xi,Xj]即为最终结果。
对于战士来说,如果他不ban端点,那么弓箭手绝对不会去ban端点(对弓箭手最好的情况就是最终留下的就是两个端点,这样距离最大),因此战士会不断将两端的点ban掉,压缩最终的区间[Xi,Xj]
对于弓箭手来说,他想要扩大最终区间,因此他会从中间ban起,每ban掉一个点,使得区间[Xi,Xj]的平均长度增长一点。但是,对于弓箭手来说,还需要注意一点,他不能让自己ban的点和战士ban的点相连。因为战士是从两端ban过来的,如果弓箭手ban的点和战士相连,则最终区间[Xi,Xj]将会急剧缩小。基于这个原因,弓箭手会选择和战士保持一个“安全距离”,而这个“安全距离”就是n/2个点(不是距离)。当每一轮战士和弓箭手选择的点都相距n/2个点时,两人ban的点肯定不会相连。

经过许多轮后,最终剩下的两个点a,b应该相距n/2个点。由于战士是足够聪明的,所以我们可以认为,无论如何他一定会将[Xi,Xj]区间压缩到最小,也就是说,最终的结果应为 [Xi,Xi+n2] 中的最小值。

将所有输入的点排序后遍历,求p[i+n/2]-p[i]的最小值即可

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>
#include <algorithm>

using namespace std;

#define MAXN    200000+100

long long p[MAXN];
int vis[MAXN];

int main()
{
    memset(vis, 0, sizeof(vis));
    int n;
    scanf("%d",&n);
    for (int i = 0; i < n; i++)
        scanf("%lld",&p[i]);
    sort(p, p + n);
    int dis = n / 2;
    long long mindiff = p[n-1]-p[0];
    for (int i = 0; i < n / 2; i++)
    {
        long long diff = p[i + dis] - p[i];
        if (diff < mindiff)
            mindiff = diff;
    }

    printf("%lld\n", mindiff);
    return 0;
}

D.Max and Bike

题意

Maxim参加了自行车比赛,比赛中选手需要先加速,然后驶过规定的区间[s,f]。自行车前轮上有一个传感器,当传感器水平位置到达s处时,将开始计时,当传感器水平位置到达f处时,停止计时。计时结果为t。已知Maxim的最大速度为v(车轮中心移动速度),前轮半径为r,求计时结果t的最小值。

解析

首先要搞明白,什么样的情况下使用的时间最少呢?

。。。

好吧其实我并不清楚。我画了几个图然后推出了到达区间时传感器在顶部的情况应该是时间最短的,但是没有考虑到最下方的情况,WA一发后才加上在底部的情况。然而我并不能进行证明。

以下是我对官方题解的翻译,大家尽量理解吧:

这题主要是解决这个问题:每次比赛的中点,传感器必须在轮子的顶部或者底部。
我们可以使用二分查找的方式来得到结果。如果轮子的中心水平移动了 c 的距离,则传感器水平移动了c+rsin(c/r)(中点时传感器在轮子的顶部)或者 crsin(c/r) (中点时传感器在轮子的底部)
渐进时间复杂度 O(nlogn) .

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>
#include <algorithm>

using namespace std;

#define MAXN    100000+100
#define PI      3.1415926535897932

int n;
long long r, v;
double esp = 1e-7;
double HF;
int check(double t)
{
    double x = t*v;
    if (x + sin(x/r)*r >= HF+esp)
        return 1;
    if (x - sin(x/r)*r >= HF + esp)
        return 1;
    return 0;
}

int main()
{
    scanf("%d%lld%lld",&n,&r,&v);
    for (int i = 0; i < n; i++)
    {
        long long s, f;
        scanf("%lld%lld",&s,&f);
        long long L = f - s;
        HF = L / 2.0;
        double l = 0, r= HF/v;
        while ((r-l)>=esp)
        {
            double mid = (l + r) / 2.0;
            if (check(mid))
                r = mid;
            else
                l = mid;
        }
        printf("%.12f\n",2*l);
    }
    return 0;
}

E.Edo and Magnets

其实我感觉这题比前面的题简单多了,,,

题意

现在地上有n个冰箱磁铁,磁铁以左下和右上坐标方式给出。现在要求设计一扇冰箱门,能将至少n-k个磁铁贴到上面。只要磁铁的中心在门范围内,就认为是能贴到门上。贴的时候磁铁间的相对位置不能改变。
求冰箱门的最小面积。(冰箱门是边长为正整数的矩形,且边缘与xy轴平行)

解析

实际上就是从n个点(磁贴的中心,坐标不一定是整数)中删去至多k个点,然后用一个最小的矩形来覆盖这些点。

显而易见,能删就删,删掉的点的数目应为k。( 0kmin(10,n1)
那么应该删哪些点呢?中间的点肯定不行,那自然是边缘的点。那么怎样算是边缘?其实就是当一个很大的矩形收缩时会先碰到的点,也就是当前点集里坐标x值最大或最小和y值最大或最小的那些点。

注意到k值最大不超过10,所以可以使用搜索来做。
我们先将所有的点拷贝一份得到两个数组cx,cy,然后两个数组分别按x和y进行排序,用lx,rx来标志当前x值边界点位置,用ly,ry来标志当前y值边界点位置。然后分别尝试移除当前x最大的点、当前x最小的点、当前y最大的点、当前y最小的点。移除k个点后,根据当前xy的最值来计算矩形面积(注意矩形边长是正整数),然后维护面积最小值mins即可。

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>
#include <algorithm>

using namespace std;

#define MAXN    100000+100

struct node
{
    int index;
    double x, y;    //中心的横坐标和纵坐标
}cx[MAXN],cy[MAXN];

bool cmpx (node a, node b)
{
    return a.x < b.x;
}

bool cmpy(node a, node b)
{
    return a.y < b.y;
}

long long mins;
int vis[MAXN];

void dfs(int k, int lx, int rx, int ly, int ry)
{
    while (vis[cx[lx].index])
        lx++;
    while (vis[cx[rx].index])
        rx--;
    while (vis[cy[ly].index])
        ly++;
    while (vis[cy[ry].index])
        ry--;
    if (k == 0)
    {
        long long s = max(1LL,(long long)(cx[rx].x - cx[lx].x)) * max(1LL,(long long)(cy[ry].y - cy[ly].y));
        if (s < mins)
            mins = s;
        return;
    }

    //===========
    //

        vis[cx[lx].index] = 1;
        dfs(k - 1, lx + 1, rx, ly, ry);
        vis[cx[lx].index] = 0;

    //=======
    //

        vis[cx[rx].index] = 1;
        dfs(k - 1, lx, rx - 1, ly, ry);
        vis[cx[rx].index] = 0;
    //===========
    //

        vis[cy[ly].index] = 1;
        dfs(k - 1, lx, rx, ly + 1, ry);
        vis[cy[ly].index] = 0;

    //=======
    //
        vis[cy[ry].index] = 1;
        dfs(k - 1, lx, rx, ly, ry - 1);
        vis[cy[ry].index] = 0;
}

int main()
{
    memset(vis,0,sizeof (vis));
    int n, k;
    scanf("%d%d",&n,&k);
    for (int i = 0; i < n; i++)
    {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        cx[i].index = i;
        cx[i].x = (x1 + x2) / 2.0;
        cx[i].y = (y1 + y2) / 2.0;
        cy[i].index = i;
        cy[i].x = cx[i].x;
        cy[i].y = cx[i].y;
    }
    sort(cx, cx + n, cmpx);
    sort(cy, cy + n, cmpy);
    mins = max(1LL,(long long)(cx[n - 1].x - cx[0].x)) * max(1LL,(long long)(cy[n - 1].y - cy[0].y));
    dfs(k, 0, n - 1, 0, n - 1);
    if (mins == 0)
        printf("1\n");
    else
        printf("%lld\n", mins);
    return 0;
}
/*
11 8
9 1 11 5
2 2 8 12
3 8 23 10
2 1 10 5
7 1 19 5
1 8 3 10
1 5 3 9
1 2 3 4
1 2 3 4
4 2 12 16
8 5 12 9
*/

竟然有转载还不附上原地址的?为了测试对方是不是爬虫我决定加上这一段

原文在CSDN上,链接http://blog.csdn.net/lincifer/article/details/50524859

还有我那连域名都还没有的博客上也有一份,链接http://115.28.240.133:8080/archives/80

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值