• 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], " 
    "[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{sumlimits_{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
    ", (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
    ", 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的操作。

  • 相关阅读:
    字符串的长度 -- js
    导入drupal中文语言包
    ubuntu修改iP地址
    人生需要苦难和敌人
    Drupal.theme未解之谜
    如何定义带下标的js数组
    smtp admin email 似乎可以考虑在
    js中的apply 和 call
    js 点号 中括号
    代码调试
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12293712.html
Copyright © 2020-2023  润新知