Educational Codeforces Round 64 (Rated for Div. 2)

比赛链接:https://codeforces.com/contest/1156

C

二分、贪心

题目链接:https://codeforces.com/contest/1156/problem/C

题面

image.png

题意

输入n,z和n个数x[1]~x[n]。
然后判断这n个数里有几对不相等的i, j。使得|x[i] - x[j]| >= z

思路

这个题贪心也能过,但我赛后补题是学习了quailty二分的思想。

二分思想

经过这道题之后我对二分有了更进一步的理解。
如果对于某道题满足这个条件:答案m满足要求,并且大于m的数有可能也满足要求,并且比m要优,虽然小于m的数也满足要求,但小于m的数相对于m来说不如m优。在这个情况下我们就可以用二分。

联系题目

对于这道题来说,先对数组排序,然后如果存在前m个数和后m个数满足要求|a[i] - a[j]| >= z,那么可能存在l对数(l > k)也满足要求。
虽然说存在r对数(r < k)也满足要求,但相对于k来说,r不如k优。
所以这道题我们可以用二分

算法思想

可以先写一个函数,判断前m个数和后m个数是不是相等。
对于n个数,最好的情况下可以找到n / 2对数,每一对数的绝对值大于等于z,最坏情况是0。所以二分的范围就是0到n / 2。
l = 0, r = n / 2为初值二分,每次算m = (l + r + 1) / 2(后面会解释为什么这里要写(l + r + 1) / 2而不是(l + r) / 2),如果m满足要求,则让l = m(最后输出l,所以不能让l = m + 1,因为可能m就是最优解了)
如果m不满足要求,那么就直接让r = m - 1因为m已经不满足要求了,所以接下来的预选区间应该缩减到r = m - 1
如此往复,直到l < r不满足为止。

二分需要注意的点(解释为什么写(l + r + 1) / 2)而不是(l + r) / 2)

一定要注意边界条件,也就是l和r只相差1的时候,要不然容易造成死循环
因为这里我们需要收缩右边界r = m - 1,所以求m的时候需要些
对于这道题来说,当r = l + 1时,为了说明方便,在此令l = 2, r = 3

  • m = (l + r + 1) / 2的情况
    l = 2, r = 3时,m = (2 + 3 + 1) / 2 = 3
    如果m不满足要求,我们会修改右边界,令r = m - 1 = 2。然后l == r == 2,退出循环,最优解是2。
    如果m满足要求,那么l = m = 3 = r,也退出循环,最优解是3。
  • m = (l + r) / 2的情况
    l = 2, r = 3时,m = (2 + 3) / 2 = 2
    如果m不满足要求,那么我们让r = m - 1 = 1,退出循环,虽然说出现了问题,但最后的结果l还是最优解
    当m满足要求时,我们会让l = m = 2,在这次操作中,l和r的值没有改变,再来一次循环,求出来的还是m = (2 + 3) / 2 = 2,l = m = 2,这样就会造成死循环。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define ms(s) memset(s, 0, sizeof(s))
const int inf = 0x3f3f3f3f;
#define LOCAL
const int maxn = 2e5 + 7;
long long n, z;
long long a[maxn];
bool ok(int m){
    for (int i = 1, j = n - m + 1; j <= n; i++, j++){
    //    cout << i << " " << j << endl;
        if (a[j] - a[i] < z){
            return false;
        }
    }
    return true;
}
int main(int argc, char * argv[]) 
{
    while (cin >> n >> z){
        for (int i = 1; i <= n; i++){
            cin >> a[i];
        }
        sort(a + 1, a + 1 + n);
        int l = 0, r = n / 2;
        while (l < r){
            int m = (l + r + 1) / 2;
            if (ok(m)){
                l = m;
            }
            else{
                r = m - 1;
            }
        }
        cout << l << endl;
    }
    return 0;
}

D

并查集,图论

赛后看官方题解补的题

题意

image.png

给一棵n个顶点n - 1条边的树,这些边中有的标记为1,有的标记为0。如果一对顶点(x, y),从x走到y,绝对没有在经过0边之后经过1边,那么它就是合法的。

官方题解翻译

把所有合法的对分成3类

  1. 只有0边的
  2. 只有1边的
  3. 两种都有的
  • 为了计算只有0边的,我们可以把原图的0边创建成一个森林。并且选择这个森林上所有属于相同联通分支的成对的边。我们可以用DSU算法或者任何图的遍历算法找所有的联通分支。
  • 对于只有1的边也可以这样子。
  • 如果一条从x到y的路径是合法的并且它包含两种类型的边,然后会存在一个点v,使得从x到v的边只包含0,从v到y的边只包含1.所以我们在这个v点上迭代,从0图中的组件选择其他顶点,从1图中选择其他顶点,并将选择他们的方法的数量添加到答案中。

解题思路

其实看了题解之后我也是很懵的,思路是这个思路,可怎么实现我还是搞不懂。
不如看官方给的代码吧,emmm看不懂,仔细一看,并查集?
对!就是并查集!
开两个并查集,一个是0边构成的并查集,一个是1边构成的并查集。也就相当于把一棵树拆分成两个子图,一个是1边构成的,一个是0边构成的(如果某个顶点没有被0边连接,则在0这个并查集里,它是个孤立的点,在1边也类似)。

参见官方题解翻译的“1”,我们对于0并查集,假设一个联通分支的元素个数是x,那它里面就含有x * (x - 1)个合法的对,1并查集也类似。
对于每个点来说,它在0并查集里所属的联通分支的元素个数为x,在1并查集里所属的联通分支的元素个数为y,那么它被当做v时合法的方案数就是(x - 1) * (y - 1)

// #pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <string>
#include <cstring>
#include <algorithm>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <ctime>
#include <vector>
#include <fstream>
#include <list>
#include <iomanip>
#include <numeric>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define ms(s) memset(s, 0, sizeof(s))
const int inf = 0x3f3f3f3f;
#define LOCAL
const int maxn = 2e5 + 7;
long long p[2][maxn]; // 代表两个并查集
long long sz[2][maxn]; // 
long long n;
long long x, y, c;
long long root(long long x, long long c){
    while (x != p[c][x]){
        p[c][x] = p[c][p[c][x]];
        x = p[c][x];
    }
    return x;
}
void uni(long long x, long long y, long long c){
    int x_root = root(x, c);
    int y_root = root(y, c);
    if (sz[c][x_root] > sz[c][y_root]){
        swap(x_root, y_root);
    }
    p[c][x_root] = p[c][y_root];
    sz[c][y_root] += sz[c][x_root];
}
void init(){
    for (int i = 1; i <= n; i++){
        p[0][i] = p[1][i] = i;
        sz[0][i] = sz[1][i] = 1;
    }
}
void print(){
    printf("p[0]\n");
    for (int i = 1; i <= n; i++){
        printf("%lld%c", p[0][i], i == n ? '\n' : ' ');
    }
    printf("p[1]\n");
    for (int i = 1; i <= n; i++){
        printf("%lld%c", p[1][i], i == n ? '\n' : ' ');
    }
    printf("sz[0]\n");
    for (int i = 1; i <= n; i++){
        printf("%lld%c", sz[0][i], i == n ? '\n' : ' ');
    }

    printf("sz[1]\n");
    for (int i = 1; i <= n; i++){
        printf("%lld%c", sz[1][i], i == n ? '\n' : ' ');
    }
}
int main(int argc, char * argv[]) 
{

    while (cin >> n){
        long long ans = 0;
        init();
        for (int i = 1; i <= n - 1; i++){
            cin >> x >> y >> c;
            uni(x, y, c);
        //  print();
        //  cout << i << " " << n << endl;
        }
        for (long long i = 1; i <= n; i++){ // 枚举每个点
            if (p[0][i] == i){ // 如果在0并查集里,它的父亲等于它自己,那么它就可以代表它所在的联通分支(它是这个联通分支的祖先)
                ans += sz[0][i] * (sz[0][i] - 1);
            }
            if (p[1][i] == i){// 如果在1并查集里,它的父亲等于它自己,那么它就可以代表它所在的联通分支(它是这个联通分支的祖先)
                ans += sz[1][i] * (sz[1][i] - 1);
            }
            ans += (sz[1][root(i, 1)] - 1) * (sz[0][root(i, 0)] - 1); // 注意这里下标是root
        }
        cout << ans << endl;
    }
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • 对于 D 题的原题意,出题人和验题人赛前都没有发现标算存在的问题,导致了许多选手的疑惑和时间的浪费,在此表示真诚的...
    _Carryon阅读 255评论 0 0
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,341评论 0 2
  • 一年级语文上册生字表 生字表一(共400字) 啊(ā)爱(ài)安(ān)岸(àn)爸(bà)八(bā)巴(bā)...
    meychang阅读 2,792评论 0 6
  • 一 当别人需求帮助的时候,更多的情况是求个当时的心理安慰,比如失恋,你说这男的真渣,在一起7年了,到最后TMD还要...
    蜡笔丢了小新阅读 225评论 7 6
  • 本是青灯不归客 却因浊酒留风尘 星光不问夜行者 岁月不负痴心人
    逸清OK阅读 277评论 0 1