Codeforces Round #618 (Div. 2)

准备回到紫色了,转了一圈回来其实实力是变强了的吧?收集了一些暂时体现不出来的经验。而且比赛这种东西的确很难说的,不会就是不会。

题目链接:https://codeforces.com/contest/1300

A - Non-zero

题意:有一个(可正可负的)整数序列,每次操作把一个数++,求最少的操作使得整个数量的sum和product都不为0。

题解:首先,每个当前就是0的数至少都需要++一次,先把这个算上。然后product就一定不会为0了,此时判断sum是否为0。若为0则选任意一个不是-1的++就可以了。显然sum为0,且不是全0的,至少有一个正数一个负数,就选一个正数++。

B - Assigning to Classes

题意:给 \(2n\) 个数的集合A,分成两个size为奇数的集合S和T,使得这两个集合的中位数的差尽可能小,求这个差。

题解:二话不说就枚举。

情况1:首先只分配1个数给S,分配完之后T的中位数显然是A集合原本的两个中位数里的一个,且显然若分配了“后半”中的一个数给S,T的中位数就是左中位数。否则就是右中位数。此时肯定是随便分配左右中位数中的一个给S是最优的。

然后分配3个数给S,很显然若三个数中有至少一个来自“前半”,且至少一个来自“后半”,则化归到情况1。否则就是三个都来自同一半,这种显然是不好的。

到这里基本已经猜出结果了,答案就是左右中位数的差。

证明:假如两个集合的中位数不是左右中位数,显然他们的中位数一个比右中位数大,另一个比左中位数小,只会更差。

C - Anu Has a Function

题意:定义 \(f(x,y)=(x|y)-y\) 。安排数列的顺序,使得 \(f(...f(f(a_1,a_2),a_3)...,an)\) 最大。

题解:易知 \(f(x,y)=x\&(~y)\) 那么意思就是选一个x,然后y的每个二进制位1都会把x的对应位置0。假设一种暴力算法 \(O(n^2)\) 枚举x然后暴力跑一遍。这里有重复的信息可以利用。考虑最后贡献答案的只可能是满足下面所有条件的:

1、x的这一位是1
2、没有任何一个y的这一位是1

换言之只有x独占这个1才是答案。所以可以分解每个位统计其1的数量。

int cnt[32];
int a[200005];
 
void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    for(int i = 1; i <= n; ++i) {
        int tmp = a[i];
        for(int k = 0; k <= 30; ++k) {
            cnt[k] += tmp & 1;
            tmp >>= 1;
        }
    }
    int maxid = -1, ans = -1;
    for(int i = 1; i <= n; ++i) {
        int tmp = a[i];
        int cur = 0;
        for(int k = 0; k <= 30; ++k) {
            if((cnt[k] == 1) && (tmp & 1))
                cur |= (1 << k);
            tmp >>= 1;
        }
        if(cur > ans) {
            ans = cur;
            maxid = i;
        }
    }
    swap(a[maxid], a[1]);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", a[i], " \n"[i == n]);
}

但是事实上 \(\&\) 运算是满足交换律结合律的,可以预处理 \(~y\) 的前缀和后缀直接得到(像极了之前那个选n-1个矩形的题,也是dls一下子秒了)。或者预处理 \(y\)\(|\) 前缀和后缀。

没什么意思就不再写一次了。

*D - Aerodynamic

写错下标真是尴尬,忘记了“经过n/2条边才是对边”。

题意:给一个严格凸包(也就是没有三点共线的凸包)P,现在可以把P平移,且要求原点一直在P中,其轨迹覆盖的位置的点集就是T。问P和T是否相似。

题解:显然,假如是奇数边的多边形就不相似,因为底边沿着P移动的时候上轨迹是一条线段,不可能和点相似。

那么现在至少是偶数边的多边形,画一画发现平行四边形满足而梯形不满足。仔细想想假如对边不平行就不会满足,因为原点沿着A边滑动的情况下,对边B覆盖的轨迹的凸包肯定是与A平行的,所以B也一定要和A平行。

对边分别平行的多边形不一定是的对边相等的。其实应该很显然!把底边平行下移,在极限的时候会缩短到退化成一个点。

int sgn(ll x) {
    return (x == 0) ? 0 : (x > 0 ? 1 : -1);
}
 
struct Point {
    ll x, y;
    Point() {}
    Point(ll x, ll y): x(x), y(y) {}
 
    ll len2() {
        return x * x + y * y;
    }
    friend Point operator-(const Point &a, const Point &b) {
        return Point(a.x - b.x, a.y - b.y);
    }
    friend ll operator*(const Point &a, const Point &b) {
        return a.x * b.x + a.y * b.y;
    }
    friend ll operator^(const Point &a, const Point &b) {
        return a.x * b.y - a.y * b.x;
    }
} P[200005];
 
struct Segment {
    Point s, t;
    Segment() {}
    Segment(const Point &s, const Point &t): s(s), t(t) {}
 
    //判断两条直线是否平行
    bool LineParallelWithLine(const Segment &l) {
        return !sgn((s - t) ^ (l.s - l.t));
    }
 
    ll len2() {
        return (s - t).len2();
    }
} L[200005];
 
int n;
int id(int x) {
    if(x > n)
        x -= n;
    return x;
}
 
void test_case() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%lld%lld", &P[i].x, &P[i].y);
    if(n % 2 == 1) {
        puts("No");
        return;
    }
    for(int i = 2; i <= n; ++i)
        L[i] = Segment(P[i], P[i - 1]);
    L[1] = Segment(P[1], P[n]);
    for(int i = 1; i <= n / 2; ++i) {
        if(L[i].len2() != L[id(i + n / 2)].len2()) {
            puts("No");
            return;
        }
        if(!L[i].LineParallelWithLine(L[id(i + n / 2)])) {
            puts("No");
            return;
        }
    }
    puts("Yes");
    return;
}

提示要准备一个ll作为坐标的几何板子。

*E - Water Balance

题意:给一个数列,每次操作可以选一段区间,然后把区间中的每个数都设为平均值。求一个字典序最小的结果。

题解:一开始想线段树,然后倍增找最长的区间,这里假在这个不满足二分性,长度为m的区间不满足不代表长度为m-1的区间不满足。显然字典序最小的结果的数列的前缀和也是字典序最小的。所以每次操作就相当于从 \(a_i=\frac{\sum\limits_{i=l}^{r}a_i}{r-l+1}=\frac{p_r-p_{l-1}}{r-l+1}\) 变成了 \(p_i=p_{l-1}+\frac{p_r-p_{l-1}}{r-l+1}\cdot(i-l+1)\) ,看上去就是一个直线的方程!或者换成 \(p_i=p_{l}+\frac{p_r-p_{l-1}}{r-l+1}\cdot(i-l)\) ,更直观。把 \((i,p_i)\) 画在数轴上,所以其实就是每次可以选择连接两个点 \(x=l,x=r\) 。由于横坐标的间隔是固定的,斜率的大小就相当于差分的大小,所以要原数组的字典序最小,就是要这个图中的一个下凸包。

int n;
ll p[1000005];
int s[1000005], top;

char ans[105];

void test_case() {
    read(n);
    for(int i = 1; i <= n; ++i)
        read(p[i]);
    if(n == 1) {
        printf("%.9f\n", (double)p[1]);
        return;
    }
    for(int i = 2; i <= n; ++i)
        p[i] += p[i - 1];
    s[++top] = 0;
    s[++top] = 1;
    for(int i = 2; i <= n; ++i) {
        while(top >= 2) {
            int s1 = s[top];
            int s2 = s[top - 1];
            ll m1 = (p[i] - p[s1]) * (s1 - s2);
            ll m2 = (p[s1] - p[s2]) * (i - s1);
            //double k1 = 1.0 * (p[i] - p[s1]) / (i - s1);
            //double k2 = 1.0 * (p[s1] - p[s2]) / (s1 - s2);
            //printf("k1=%.8f k2=%.8f\n", k1, k2);
            if(m1 <= m2)
                --top;
            else
                break;
        }
        s[++top] = i;
    }
    for(int j = 1; j < top; ++j) {
        double avg = 1.0 * (p[s[j + 1]] - p[s[j]]) / (s[j + 1] - s[j]);
        sprintf(ans, "%.10f", avg);
        int tmp = s[j + 1] - s[j];
        while(tmp--)
            puts(ans);
    }
    return;
}

收获:
1、说白了是因为我不会斜率优化才看不出来的。
2、快速输出是真的快,只需要800ms!
3、用前缀和求出下凸包之后,并不一定要用前缀和差分进行构造答案,可以直接统计输出。
4、貌似在用凸包的时候,不需要进行那种+1和-1的操作。

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