Codeforces Round 1009(Div.3) ABCDEF题解

这场打的太好了,本来懒的写题解了,但是rk113,激动坏了写一篇

A、Draw a Square

题意:给出四个在坐标轴上面的坐标,判断这四个点连成的是否是正方形

正方形和长方形的对角线相等,但以交点分割的两条对角线都相等才是正方形,直接判断四个点到原点距离是否相等即可

void solve(){
    int a,b,c,d;cin>>a>>b>>c>>d;
    if(a == b && b == c && c == d){
        cout<<"Yes"<<endl;
    }
    else{
        cout<<"No"<<endl;
    }
}

B、The Third Side

题意:给定一个数组a,有一个操作:可以选择两个数ai,aja_i,a_jai,aj,然后自己选择一个xxx,这三个数要满足能够组成非退化三角形,然后删除ai,aja_i,a_jai,aj,将x添加到数列的末尾,求最后剩下的数的最大值。

首先一个非退化三角形若给出两条边a,ba,ba,b,那么第三条边xxx要满足a−b<x<a+ba-b<x<a+bab<x<a+b,题目让求最大值,那么就贪心的将每两个数都去和a+b−1a+b-1a+b1组合,这样最后剩下的数就是最大值。

void solve(){
    int n;cin>>n;
    vector<int> a(n);
    for(int i = 0;i<n;i++){
        cin>>a[i];
    }
    for(int i = 0;i<a.size();i+=2){
        if(i+1 == a.size()){
            break;
        }
        a.pb(a[i]+a[i+1]-1);
    }
    cout<<a.back()<<endl;
}

C、XOR and Triangle

题意:给出一个正整数xxx,找出一个正整数yyy,使得y<xy<xy<x且以 x,y,x⊕yx,y,x\oplus yx,y,xy 作为三角形的三条边可以构成非退化三角形。

这个题的数据范围很宽松,ttt只有2000,且x<=109x<=10^9x<=109,先打一个表发现如果一个数是完全平方数或完全平方数−1-11,那么没有复合条件的yyy,其他的数通过打一个表发现在很小的数中就有满足条件的数,偶尔会有特殊情况,但是这些数也不是非常大,这里也没有去证明,但是保不准会被hack(qaq,不要hack我啊),所以我就直接先与处理一下没有符合条件的数,再从小到大枚举每个数找到复合条件的yyy

不过我的队友的做法应该很稳,是O(31*30t)的,太晚了没时间推一遍了,明天有时间会将更快的做法补充上。

map<int,int> mp;
void solve(){
    int n;cin>>n;
    if(mp[n]){
        cout<<"-1"<<endl;
        return;
    }
    for(int i = 0;i<n;i++){
        if(n-i < (n ^ i) && (n^i) < n+i){
            cout<<i<<endl;
            return;
        }
    }
}
signed main(){
    cin.tie(0)->ios_base::sync_with_stdio(0);
    int t = 1;
    i64 ans = 1;
    while(ans*2<1e10){
        mp[ans*2]++;
        mp[ans*2-1]++;
        ans*=2;
    }
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

D. Counting Points

题意:平面上有若干个圆心位于xxx轴上的圆,求至少被一个圆包含(包括边界)的整数点数目,每个圆由圆心坐标和半径决定,所有圆的半径之和为m。

实现起来很麻烦的一个题,每个圆对每个可能的yyy值(k)生成水平线段区间,对于每个yyy,计算该圆在xxx轴上的覆盖范围,生成的起点和终点,即表示在该区间内,该圆能贡献的最大yyy值为kkk,之后用扫描线算法,按xxx坐标从小到大处理区间,维护当前所有活动的kkk值,并取最大值,每个区间的贡献为2k+12k+12k+1,将所有贡献加起来即可

struct node {
    long long x;
    int type; 
    long long k;

    bool operator<(const node& other) const {
        if (x != other.x) return x < other.x;
        return type < other.type; 
    }
};
void solve(){
    int n;
    i64 m;
    cin >> n >> m;
    vector<i64> x(n), r(n);
    for (int i = 0; i < n; i++) cin >> x[i];
    for (int i = 0; i < n; i++) cin >> r[i];
    vector<node> a;
    for (int i = 0; i < n; i++) {
        if (x[i] - r[i] > x[i] + r[i]) continue;
        for (i64 k = 0; k <= r[i]; k++) {
            i64 val = r[i] * r[i] - k * k;
            if (val < 0) break;
            i64 l1 = x[i] - (i64)sqrt(val);
            i64 r1 = x[i] + (i64)sqrt(val);
            i64 l2 = max(l1, x[i] - r[i]);
            i64 r2 = min(r1, x[i] + r[i]);
            if (l2 > r2) continue;
            a.push_back({l2, 1, k});
            a.push_back({r2 + 1, -1, k});
        }
    }
    sort(a.begin(), a.end());
    multiset<i64> k;
    i64 ans = 0;
    i64 prex = -1e18;
    for (const auto& e : a) {
        i64 cx = e.x;
        if (cx > prex) {
            if (!k.empty()) {
                ans += (2 * *k.rbegin() + 1) * (cx - prex);
            }
            prex = cx;
        }
        if (e.type == 1) {
            k.insert(e.k);
        } else {
            auto it = k.find(e.k);
            if (it != k.end()) {
                k.erase(it);
            }
        }
    }
    cout << ans << '\n';
}

E. Empty Triangle

题意:交互题,在二维坐标中有隐藏的n个点,你需要找出三个点,构成三角形且该三角形内部没有额外的点,询问次数不超过75次,可以询问三个点的下标,若询问的三角形内部没有点的话返回0,若有点则返回点的下标。

这个题更玄学了,思维难度其实很低,就是题面太长了看了好久,其实我们要做的就是缩小三角形的面积直到满足题意,系统会告诉你三角形内部的点,而保持原来的两个点不动,将其中一个点改为原三角形内部的点是一定会使面积减小的且包含的点也会减少。如下图所示
请添加图片描述
黑色表示原来的三角形,红色表示利用系统给出的内部点组成的三角形,这样慢慢搞一定会使点慢慢减小的,那么又有这种情况
请添加图片描述
如果是这种情况,因为如果三角形内部有很多点,系统会任意选择其中的一个,如果你运气非常不好,他每次都选择最靠上面的点,而你总是保持下面这两个点不变,那么询问次数一定会超过限制。
这个题我罚时很多,尝试了三个数总替换第一个,第二个,第三个,还有三个点轮流替换,但都是答案错误,最后没有报着通过的希望去用了一个随机数,然后就过了。。。。

题目里面也说了不支持hack所以这个题很难评

引用官解中的证明:Wolfram Alpha 将正确猜测的次数建模为二项分布后,我们可以估算出一个测试用例的失败概率不大于 2.4⋅10−72.4⋅10^{−7}2.4107。由于 (1−2.4⋅10−7)700>0.9998(1−2.4⋅10^{−7})^{700}>0.9998(12.4107)700>0.9998 ,我们发现该解决方案在至少一个测试用例中失败的概率严格小于 0.020.02%0.02

int getRand(int min, int max) {
    return (rand() % (max - min + 1)) + min;
}

void solve() {
    int n;cin >> n;
    vector<int> p = {1, 2, 3};
    for (int q = 0; q < 75; ++q) {
        cout << "? " << p[0] << ' ' << p[1] << ' ' << p[2] << endl;
        cout.flush();
        int tmp;cin>>tmp;
        if (tmp == 0) {
            cout << "! " << p[0] << ' ' << p[1] << ' ' << p[2] << endl;
            cout.flush();
            return;
        }
        p[getRand(0,10000)%3] = tmp;
    }
    cout << "! " << p[0] << ' ' << p[1] << ' ' << p[2] << endl;
    cout.flush();
}

F. Counting Necessary Nodes

题意:使用四叉树节点覆盖给定的矩形区域,并找到所需的最少节点数目。四叉树的每个节点对应一个边长为 2k2^k2k的正方形区域,且每个区域的起始坐标为 a⋅2ka \cdot 2^ka2k。通过选择最少的节点,使得它们的并集恰好覆盖给定的矩形区域。
重点在理解题意,只要能看懂题目说的是什么意思,琢磨琢磨就做出来了。
每个节点对应一个边长为2k2^k2k的正方形,其坐标为[a⋅2k,(a+1)⋅2k]×[b⋅2k,(b+1)⋅2k][a \cdot 2^k,(a+1) \cdot 2^k] \times [b \cdot 2^k, (b+1) \cdot 2^k][a2k,(a+1)2k]×[b2k,(b+1)2k],较大的节点可以覆盖四个较小的子节点,对于每个可能的层级kkk,计算该层能够完全覆盖给定趋于的节点数目,较大的节点覆盖趋于会包含其子节点,因此需要考虑利用容斥原理减去重复覆盖的部分。
每一层的贡献即为该层的节点数目减去四倍下一层节点的数目,通过累加所有层的贡献,得到最终的最小子节点的数目。
对于层级kkk,边长为s=2ks = 2^ks=2k。计算在xxx轴和yyy轴方向上能够完全覆盖给定趋于的节点数目:
x轴方向:满足a⋅s≥l1且(a+1)⋅s≤r1的整数a的数目y轴方向:满足b⋅s≥l2且(b+1)⋅s≤r2的整数b的数目 x轴方向:满足a \cdot s \ge l_1且(a+1)\cdot s \le r_1的整数a的数目\\ y轴方向:满足 b \cdot s \ge l_2且(b+1)\cdot s \le r_2的整数b的数目 x轴方向:满足asl1(a+1)sr1的整数a的数目y轴方向:满足bsl2(b+1)sr2的整数b的数目
该层节点数目为两个方向数目的乘积aka_kak,总贡献即为所有层的贡献ak−4⋅ak+1a_k-4\cdot a_{k+1}ak4ak+1
所以对于每个层级kkk,计算xxxyyy轴方向的节点数目,并记录在数组aaa中,遍历所有层,累加每一层的贡献,得到最终最小节点数目。时间复杂度为常数时间O(31)O(31)O(31)

void solve(){
    i64 l1, r1, l2, r2;
    cin >> l1 >> r1 >> l2 >> r2;
    vector<i64> a(32, 0); 
    for (int k = 0; k <= 30; ++k) {
        i64 x = 1LL << k;
        i64 ansx = max(0LL, (r1 / x)  - (l1 + x - 1) / x);
        i64 ansy = max(0LL, (r2 / x)  - (l2 + x - 1) / x);
        a[k] = ansx * ansy;
    }
    i64 cnt = 0;
    for (int k = 0; k <= 30; ++k) {
        cnt += a[k];
        if (k + 1 <= 30) {
            cnt -= 4 * a[k + 1];
        }
    }
    cout << cnt << '\n';
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值