Codeforces Round #614 (Div. 2)

21:35刚刚降落广州白云机场,在手机上打了个Python刷个场次,顺便掉个蓝色献祭一下这个学期的考试分数。

A - ConneR and the A.R.C. Markland-N

题意:找不出现在k个给定数字之中的,值域在[1,n]的离s最近的数字的距离。保证k<n。

题解:

1.常数比较低的做法:先排序,然后看看s是不是在k个数字里面,不在的话直接就0,否则一定在里面。然后给ans赋值初始值,取k个数字的两端。然后遍历每个数字,只要它和上一个数字不连续,那么中间可以贪心一个位置(然后break掉,但是懒)。

int a[1005];
void test_case() {
    int n, s, k;
    scanf("%d%d%d", &n, &s, &k);
    for(int i = 1; i <= k; ++i)
        scanf("%d", &a[i]);
    sort(a + 1, a + 1 + k);
    int pos = 0;
    for(int i = 1; i <= k; ++i) {
        if(a[i] == s) {
            pos = i;
            break;
        }
    }
    if(pos == 0) {
        puts("0");
        return;
    }
    int ans = INF;
    if(a[1] != 1)
        ans = s - (a[1] - 1);
    if(a[k] != n)
        ans = min(ans, a[k] + 1 - s);
    for(int i = pos - 1; i >= 1; --i) {
        if(a[i] == a[i + 1] - 1)
            continue;
        ans = min(ans, s - (a[i + 1] - 1));
    }
    for(int i = pos + 1; i <= k; ++i) {
        if(a[i] == a[i - 1] + 1)
            continue;
        ans = min(ans, a[i - 1] + 1 - s);
    }
    printf("%d\n", ans);
}

2.因为删除至多k个数字,那么在s的附近k+1个不越界的数字全部插进set,然后遍历这个数组把不要的删掉,找前驱和后继。多组数据记得clear()掉。

set<int> _set;
void test_case() {
    _set.clear();
    int n, s, k;
    scanf("%d%d%d", &n, &s, &k);
    int F = max(1, s - k - 2);
    int C = min(n, s + k + 2);
    for(int i = F; i <= C; ++i)
        _set.insert(i);
    for(int i = 1; i <= k; ++i) {
        int ai;
        scanf("%d", &ai);
        _set.erase(ai);
    }
    auto it = _set.lower_bound(s);
    int ans = INF;
    if(it != _set.end())
        ans = *it - s;
    if(it != _set.begin()) {
        --it;
        ans = min(ans, s - *it);
    }
    printf("%d\n", ans);
}

B - JOE is on TV!

题意:有s个人,每次可以搞掉其中的t个,获得t/s的分数,求把s个人全部搞掉得到的最多的分数。

题解:由不等式:

\[\frac{t}{s}+\frac{1}{s-1}\geq\frac{t+1}{s} \]

以及数学归纳法得到。

这道题不卡精度,随便过,怎么暴力怎么来。

void test_case() {
    int n;
    scanf("%d", &n);
    double ans = 0;
    for(int i = 1; i <= n; ++i)
        ans += 1.0 / i;
    printf("%.8f\n", ans);
}

C - NEKO's Maze Game

题意:有一个2行n列的棋盘格迷宫,要求从(1,1)走到(2,n),有q次修改,每次修改把某个点(r,c)的可行/不可行状态翻转,问每次修改后还能不能从起点到达终点。

题解:这个形式,只有两行,很明显满足无后效性。首先判断是不是有同一列的两个都被不可行,这个可以O(1)维护出来,否则导致出现No的就必须是一个斜方向的格,这个也可以O(1)维护出来。

int sta[2][100005];
int vis[2][100005][3];
int cnt;

void test_case() {
    int n, q;
    scanf("%d%d", &n, &q);
    while(q--) {
        int r, c;
        scanf("%d%d", &r, &c);
        --r;
        if(sta[r][c] == 0) {
            sta[r][c] = 1;
            vis[1 - r][c + 1][0] = 1;
            vis[1 - r][c][1] = 1;
            vis[1 - r][c - 1][2] = 1;
            if(vis[1 - r][c + 1][0] && sta[1 - r][c + 1])
                ++cnt;
            if(vis[1 - r][c][1] && sta[1 - r][c])
                ++cnt;
            if(vis[1 - r][c - 1][2] && sta[1 - r][c - 1])
                ++cnt;
        } else {
            if(vis[1 - r][c + 1][0] && sta[1 - r][c + 1])
                --cnt;
            if(vis[1 - r][c][1] && sta[1 - r][c])
                --cnt;
            if(vis[1 - r][c - 1][2] && sta[1 - r][c - 1])
                --cnt;
            sta[r][c] = 0;
            vis[1 - r][c + 1][0] = 0;
            vis[1 - r][c][1] = 0;
            vis[1 - r][c - 1][2] = 0;
        }
        if(cnt)
            puts("No");
        else
            puts("Yes");
    }
}

突然懂怎么做了。

题意:有若干个点,第 \(0\) 号点是 \((x_0,y_0)\) ,若 \(i>0\) ,则第 \(i\)号点是 \((x_i,y_i)\) ,且有 \(x_i=a_xx_{i-1}+b_x\)\(y_i=a_yy_{i-1}+b_y\) ,这里 \(2\leq a_x,a_y \leq 100\)\(0\leq b_x,b_y \leq 10^{16}\) 。开始在 \((x_s,y_s)\) ,每步移动1格(曼哈顿距离),求最多经过多少个点。

考虑相邻的三个点的斜率: \(k_2=\frac{y_2-y_1}{x_2-x_1}\)\(k_1=\frac{y_1-y_0}{x_1-x_0}\)\(k_2=\frac{(a_y-1)y_1+b_y}{(a_x-1)x_1+b_x}=\frac{(a_y-1)(a_yy_0+b_y)+b_y}{(a_x-1)(a_xx_0+b_x)+b_x}=\frac{(a_y^2-a_y)y_0+a_yb_y}{(a_x^2-a_x)x_0+a_xb_x}\)\(k_1=\frac{(a_y-1)y_0+b_y}{(a_x-1)x_0+b_x}\)\(k2=\frac{a_y}{a_x}k1\) ,推广到所有的点,可知这些点组成的曲线不是上凸就是下凸。这样形状的线必定有一条路可以不花费额外的代价经过中途所有的点。事实上就算不是上下凸的,只需要满足每次的x和y都比前一个大,这样子的曲线都是可以不花费额外的代价经过中途所有的点。因此,得到:

推论1:取的点必定连续。因为假如取的点不连续,由上面的观察,可以有一条路顺便把中间的点也取了,不会更差。

推论2:最多进行一次“折返”,因为进行两次“折返”白白浪费了在中间的路程。

由于 \(2\leq a_x,a_y \leq 100\) 所以最多就60多个点,枚举第一次取的点k,然后从k出发两边枚举一下区间。

ll x[205], y[205];
ll ax, ay, bx, by;
ll dis[205][205];

const ll MAX = 3e16;

void test_case() {
    int n = 0;
    scanf("%lld%lld%lld%lld%lld%lld", &x[0], &y[0], &ax, &ay, &bx, &by);
    while(ax * x[n] + bx <= MAX && ay * y[n] <= MAX) {
        ++n;
        x[n] = ax * x[n - 1] + bx;
        y[n] = ay * y[n - 1] + by;
    }
    for(int i = 0; i <= n; ++i) {
        for(int j = 0; j <= n; ++j)
            dis[i][j] = abs(x[i] - x[j]) + abs(y[i] - y[j]);
    }
    ll xs, ys, t;
    scanf("%lld%lld%lld", &xs, &ys, &t);
    int ans = 0;
    for(int k = 0; k <= n; ++k) {
        ll disk = abs(x[k] - xs) + abs(y[k] - ys);
        for(int i = 0; i <= k; ++i) {
            for(int j = k; j <= n; ++j) {
                if(disk + min(dis[k][i], dis[k][j]) + dis[i][j] <= t)
                    ans = max(ans, j - i + 1);
            }
        }
    }
    printf("%d\n", ans);
}

收获:x和y均单调递增的点序列,像是“偏序”一样的,确定首尾之后,连续的一段都可以被经过。

posted @ 2020-01-20 02:02  KisekiPurin2019  阅读(127)  评论(0编辑  收藏  举报