Educational Codeforces Round 50 (Rated for Div. 2)

A. Function Height

在一个坐标轴上, 给一个n,在2n+1长度的轴上,允许调高任意一个奇数的点的高度,形成一个三角形,每个三角形会框出一个区域。所有跳高过的点的最高点,称为坐标最高点。求,给定n,要圈出k面积的区域,求怎么调整每个点的高度,使得最高点最小。

嗯。。。说实话题目挺绕的,但是其实非常简单。。。不管如何调整高度,框出来的三角形面积总是跟高度相同,因为:

S = 2 * h / 2 = h

然后要圈出k面积,只需要取 k/n 的高度就行了。这个是数学形式上的最小最高点。但是由于题目要求整数,那就向上取整一下,就ok了。

#include "bits/stdc++.h"
using namespace std;
int main(int argc, char *argv[])
{
    long long n,k;
    cin >> n >> k;

    long long ret = k / n;
    if(ret * n != k)
    {
        ret ++;
    }
    cout << ret;
    return 0;
}

B. Diagonal Walking v.2

这个题目当时脑子抽了,没有想明白,一直WA,等做完了,看看别人的思路,才明白,这么简单。

总的来说,思路就是,结果只有三种情况,k, k -1, k-2。

为什么呢?

首先我们来看下行走策略。

1,直接斜着走。嗯,这个当然很好

2,如果同一个方向走超过两个单位,那么,实际上可以变成两个斜线走。比如,从(0,0)走到(0,2),可以这样走。先走到(1,1),然后再走到(0,2)。

3,如果同一个方向走一个单位。嗯,没法了,只能直线走。

4,还有一种,为了走够k步,不得不走直线,浪费步数,比如k为3,目标为(0,0),那么必须拿出两部来走直线。即(1,1) (1,0) (0,0)的走法。

5,唯一一种不能到达的,就是k < m + n了。

所以总体策略很简单。我们这么做:

1,先走斜线,走min(n,m)步,此时跟n或者m里面一个持水平

2,走直线,走max(n,m) - min(n,m)步,用斜线走,此时应该到(m,n)附近或者差1一步。

3,如果m+n为奇数,可以猜测,要么min(n,m)是奇数,要么max(n,m) 是奇数。但是无论如何,max(m,n)-min(m,n)一定是奇数。所以走直线这里,必定需要有一步直线,回到(m,n)。所以如果m+n为奇数,最后的答案就是k-1

4,如果m+n为偶数,要么刚好走到(m,n),剩余的步数为偶数。那就k步都可以走斜线。

5,要么走到(m,n)的时候还多出一步。那这个时候只能拿出两个直线,来消耗这个多出来的斜线了。这个时候就只有k-2步可以走斜线了。而这种情况的判定,可以看第二个步骤,走直线那里。如果max(m,n) - min(m,n)为奇数,此时说明直线不能通过走两步斜线刚好到达,需要多两步。

#include "bits/stdc++.h"
using namespace std;

void debug(std::string p)
{
    cout << p;
}

int main(int argc, char *argv[])
{
    int q = 0;
    cin >> q;
    long long n, m, k;
    while(q--)
    {
        cin >> n >> m >> k;
        long long ret;
        if(n > k || m > k)
        {
            cout << -1 << endl;
            continue;
        }
        if((m + n) % 2 == 0) 
        {
            //
            ret = k;
            if((2 + std::llabs(m - k)) % 2 == 1)
            {
                ret -= 2;
            }
        }
        else
        {
            ret = k - 1;
        }
        cout << ret << endl;
    }
    return 0;
}

C. Classy Numbers

求[l,r]范围内,有多少数字满足只有三个非零数字位的条件。如12000,10001都满足。但是1111不满足。

把问题分两部分看,分别求小于 l + 1和小于r的范围内,有多少个数字满足要求,然后相减即可。这就转换为如何求小于数字x的数字数量了。

这个计数方法大致就是:

1,从高位开始,先选第一个数字n,定下来,这个数字非零。

2,剩下的几位,选0,1,2位非零数字,在里面填数字。

3,最高位变动,其变动范围为1到n-1,每个为定好之后,按照2处理。

4,重复第1为,但是这次定下的是第一第二位。

#include "bits/stdc++.h"
#include <cmath> 
using namespace std;

long long C(long long n,long long m){
    long long ret = 1l;
    for(long long i = 0l; i < m; i ++)
    {
        ret *= (n - i);
    }
    for(long long i = 0l; i < m; i ++)
    {
        ret /= (i + 1);
    }
    return ret;
}

long long my_pow(long long a, long long b)
{
    if(b == 1)
    {
        return a;
    }
    else if(b == 0)
    {
        return 1;
    }
    long long halRet = my_pow(a, b/2);
    if(b % 2 == 1)
    {
        return halRet * halRet * a;
    }
    else
    {
        return halRet * halRet;
    }
}

long long f(long long x)
{
    stringstream ss;
    ss << x;
    string s;
    ss >> s;
    reverse(s.begin(),s.end());
    long long ret = 0, nonzero = 0;
    for(int i = s.length() - 1; i >= 0; i--)
    {
        int d = s[i] - '0';
        if(d != 0)
        {
            for(int k = 0; k < (4 - nonzero); k ++)
            {
                ret += C(i,k) * my_pow(9, k);
            }
            nonzero++;
            for(int k = 0; k < (4 - nonzero); k ++)
            {
                ret += (d - 1) * C(i,k) * my_pow(9, k);
            }
        }
        if(nonzero > 3)
        {
            break;
        }
    }
    return ret;
}

int main(int argc, char *argv[])
{
    long long q = 0;
    cin >> q;
    while(q--)
    {
        long long l, r;
        cin >> l >> r;
        cout << f(r + 1) - f(l) << endl;
    }
    return 0;
}

D. Vasya and Arrays

给定两个数组,允许合并连续的数字。两个数字分别合并,合并完之后,让两个数组尽可能长。

由于所有输入的数字都是正整数,且合并的时候不能移动位置。所以可以用贪心算法,轮流合并即可。

#include "bits/stdc++.h"
using namespace std;
long long a[300005];
long long b[300005];
long long c[300005];

int main(int argc, char *argv[])
{
    int na, nb;
    long long sum = 0;
    cin >> na;
    for (int i = 0; i < na; ++i) {
        scanf("%d", &a[i]);
        sum += a[i];
    }
    cin >> nb;
    for (int i = 0; i < nb; ++i) {
        scanf("%d", &b[i]);
        sum -= b[i];
    }
    if(sum != 0)
    {
        cout << -1;
        return 0;
    }
    int ret = 0;
    long long sa = a[0], sb =b[0];
    int i,j;
    for(i = 1, j = 1; i <= na && j <= nb;)
    {
        while(sa != sb && i <= na && j <= nb)
        {
            if(sa < sb)
            {
                sa += a[i];
                i++;
            }
            else
            {
                sb += b[j];
                j++;
            }
        }
        if(sa == sb)
        {
            c[ret] = sa;
            ret++;
            sa = sb = 0;
            if(i >= na || j >= nb)
            {
                break;
            }
            sa = a[i];
            sb = b[j];
            i++;
            j++;
        }
    }
    if(i == na && j == nb)
    {
        cout << ret << endl;
    }
    else
    {
        cout << -1 << endl;
    }

    return 0;
}

E. Covered Points

给定很多线段。求这些线段覆盖到的整数点。这些线段会有交错,但是肯定不在一条线上。

先说第一部分。求一根线覆盖了多少整数点。这个点数很简单,__gcd(abs(dx), abs(dy)) + 1就是了。为什么?

令gcd(abs(dx),abs(dy))为gcd,必然有gcd a = abs(dx), gcd b = abs(dy),a,b都为整数。此时,x每前进a长度,y必定前b长度。一共可以前进gcd次。

然后,就是求交错的整数点的数量了。这个,就是求交线,然后确认是不是整数,然后看看是不是在线段内,就ok了。这个套个模版,就ok。

#include<bits/stdc++.h>
using namespace std;
#define N 1005
typedef struct _point
{
    long x;
    long y;
}point;

struct seg{
    int x1, y1, x2, y2;
    seg(){};
};

struct line{
    long long A, B, C;
    line(){};
    line(seg a){
        A = a.y1 - a.y2;
        B = a.x2 - a.x1;
        C = -A * a.x1 - B * a.y1;
    };
};

long long det(long long a, long long b, long long c, long long d){
    return a * d - b * c;
}


//求两个线是否有交集
bool inter_for_line(seg a, seg b, int& x, int& y){
    line l1(a), l2(b);
    long long dx = det(l1.C, l1.B, l2.C, l2.B);
    long long dy = det(l1.A, l1.C, l2.A, l2.C);
    long long d = det(l1.A, l1.B, l2.A, l2.B);
    if (d == 0)
        return false;
    if (dx % d != 0 || dy % d != 0)
        return false;
    x = -dx / d;
    y = -dy / d;
    return true;
}

bool in_seg(int x, int l, int r){
    if (l > r) swap(l, r);
    return (l <= x && x <= r);
}

//求两个线段是否有交集
bool inter_for_seg(seg a, seg b, int& x, int& y)
{
    if(inter_for_line(a,b,x,y))
    {
        if (!in_seg(x, a.x1, a.x2) || !in_seg(y, a.y1, a.y2))
            return false;
        if (!in_seg(x, b.x1, b.x2) || !in_seg(y, b.y1, b.y2))
            return false;
        return true;
    }
    return false;
}

//求线段内的整数坐标数
int get_int_coordinate_num(seg a){
    int dx = a.x1 - a.x2;
    int dy = a.y1 - a.y2;
    if(dx == 0)
    {
        return abs(dy) + 1;
    }
    else if(dy == 0)
    {
        return abs(dx) + 1;
    }
    return __gcd(abs(dx), abs(dy)) + 1;
}

int main(int argc, char *argv[])
{
    int n = 0;
    int x1,y1,x2,y2;
    vector<seg> a;
    seg tmp;
    cin >> n;
    for (int i = 0; i < n; ++i) 
    {
        scanf("%d %d %d %d", &x1,&y1, &x2,&y2);
        tmp.x1 = x1;
        tmp.x2 = x2;
        tmp.y1 = y1;
        tmp.y2 = y2;
        a.push_back(tmp);
    }
    long long cnt = 0;
    for (int i = 0; i < n; ++i) 
    {
        set<pair<int, int> > pts;
        cnt += get_int_coordinate_num(a[i]);
        for(int j = 0; j < i; j++)
        {
            if(inter_for_seg(a[i], a[j], x1,y1))
            {
                pts.insert(pair<int, int>(x1,y1)); 
            }
        }
        cnt -= pts.size();
    }
    cout << cnt << endl;
    return 0;
}

G. Sources and Sinks

有n个点,m个有向线段构成有向图。然后他们最多只有20个source和sink,source就是入度为0,sink是出度为0。source和sink数量保证相同。求,以任意方式从sink添加一个线段到source,是不是可以构成强联通。

题目看起来点线很多,但是,由于是有向图,所以可以肯定,对于非source和sink的点,必定至少有一个source点能到达该点,并且该点一定有一个方法,能到达sink点。

所以题目缩减到了40个source和sink点的问题。

更进一步,什么时候会有非强联通图呢?那就是有一个子图,这个子图内,所有的sink,都连接到本子图source。此时,本土的source,必定会有某些source不能到达(当然,还有可能有其他点,但是source点肯定不能到达)。

所以,只要枚举了所有可能,没有这种自图,就能说yes了。

这里还TLE了几次,分别是因为用了map来组织g数组,以及用cin来读输入。事实证明O(logn)和O(1)还是有差异的。。。。同时也证明自己偷懒就得浪费运行时时间的真理。。。

#include "bits/stdc++.h"
using namespace std;

int input[1000005];
int output[1000005];
bool reachable[21][1000005];
vector<int> tail;
// use an array will be much faster then map
std::vector<int> g[1000005];

void dfs(int sourceNumber, int source)
{
    reachable[sourceNumber][source] = true;
    for(auto x:g[source])
    {
        if(reachable[sourceNumber][x] == false)
        {
            dfs(sourceNumber, x);
        }
    }
}

int main(int argc, char *argv[])
{
    int n,m;
    int from, to;
    scanf("%d %d", &n, &m);
    memset(input,0, sizeof(input));
    memset(output,0, sizeof(output));
    memset(reachable, 0 , sizeof(reachable));
    tail.clear();
    for (int i = 0; i < m; ++i) {
        //scanf is faster than cin!
        scanf("%d %d", &from, &to);
        g[from].push_back(to);
        input[to]++;
        output[from]++;
    }
    int sourceNumber = 0;
    for (int i = 1; i <= n; ++i) {
        if(input[i] == 0)
        {
            dfs(sourceNumber, i);
            sourceNumber++;
        }
        if(output[i] == 0)
        {
            tail.push_back(i);
        }
    }
    set<int> s;
    for(int i = 1; i < (1 << sourceNumber) - 1; i ++)
    {
        s.clear();
        int cnt = 0;
        for (int j = 0; j < sourceNumber; ++j) {
            if(i & ( 1<<j))
            {
                //chech this source
                cnt++;
                for(auto x:tail)
                {
                    if(reachable[j][x] == true)
                    {
                        s.insert(x);
                    }
                }
            }
        }
        if(s.size() <= cnt)
        {
            cout << "NO" << endl;
            return 0;
        }
    }
    cout << "YES" << endl;
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值