AtCoder Beginner Contest 294(abc 294)(A~F)

文章提供了几道编程竞赛中的题目解析,涉及到的算法包括位运算、ASCII艺术、数组合并、集合操作、双指针以及浮点数二分查找。题目涵盖了数组处理、集合操作和浮点数计算等编程基础,同时强调了在解决实际问题中如何应用这些算法和数据结构。
摘要由CSDN通过智能技术生成

https://atcoder.jp/contests/abc294

A - Filter
不解释

void solve(){
    int n;
    cin >> n;
    for(int i = 1, k; i <= n; i ++){
        cin >> k;
        if(!(k & 1))
            cout << k << ' ';
    }
}

B - ASCII Art
不解释

void solve(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= m; j ++){
            int k;
            cin >> k;
            if(k == 0)
                cout << '.';
            else cout << (char)('A' + k - 1);
        }
        cout << endl;
    }
}

C - Merge Sequences
题太长了,我解释一下题意吧
给你两个数组 A , B A,B A,B,然后你把这两个数组放到数组 C C C里面,排序后,问你原来 A , B A,B A,B数组中的数字在 C C C中的位置,保证 A , B A,B A,B中的每一个数字都不一样

void solve(){
    int n, m;
    cin >> n >> m;
    std::vector<int> a(n), b(m), c;
    map<int, int> mp;
    int idx = 0;
    for(auto &i : a) cin >> i, c.pb(i);
    for(auto &i : b) cin >> i, c.pb(i);
    sort(c.begin(), c.end());
    for(auto i : c)
        mp[i] = ++ idx;
    for(auto i : a) cout << mp[i] << ' ';
    cout << endl;
    for(auto i : b) cout << mp[i] << ' ';
    cout << endl;  
}

D - Bank
还是说一下题意

给你一个序列 1 ∼ N 1\sim N 1N
给你三个操作:
1,选一个没选过的第一个数字放到集合里面
2,给你 x x x,删除集合里面的 x x x
3,问你集合里面最小的数字

所以全是set的操作,所以直接模拟就行

void solve(){
    int n, q;
    cin >> n >> q;
    int id = 1;
    set<int> s;
    while(q --){
        int st;
        cin >> st;
        if(st == 1 && id <= n){
            s.insert(id ++);
        }
        else if(st == 2){
            int x; cin >> x;
            s.erase(x);
        }
        else{
            cout << *s.begin() << endl;
        }
    }
}

E - 2xN Grid
这个题面也不是很好读

给你 L , N 1 , N 2 L,N1,N2 L,N1,N2,然后给你 ( v 1 i , l 1 i )    ( i = 1 ∼ N 1 ) (v_{1i}, l_{1i})\ \ (i = 1\sim N1) (v1i,l1i)  (i=1N1) ( v 2 i , l 2 i )    ( i = 1 ∼ N 2 ) (v_{2i}, l_{2i})\ \ (i = 1\sim N2) (v2i,l2i)  (i=1N2)
(但是很苟的是,这俩 v , l v,l v,l全给一列了,就很难看)
意味着,从位置1开始,有 l i l_i li长度的数字全是 v i v_i vi
所以上下两行,就可以表示成数组 a L a_L aL和数组 b L b_L bL
他给了两行,所以就问你,对于 1 ∼ L 1\sim L 1L中有多少个位置 j j j中, a j = b j a_j=b_j aj=bj

所以直接双指针指就可以了,把数组 a a a变成两维, a i 1 a_{i1} ai1表示当前段的右边界的位置,所以 a ( i − 1 ) 1 + 1 a_{(i - 1)1}+1 a(i1)1+1也意味着 a i 1 a_{i1} ai1的左边界, a i 0 a_{i0} ai0就表示当前段的数字是啥
直接双指针就行了

const int N = 1e5 + 10;
ll a[N][2], b[N][2], cnt;
int n1, n2;
ll L;
 
void solve(){
    cin >> L >> n1 >> n2;
    for(int i = 1; i <= n1; i ++) cin >> a[i][0] >> a[i][1], a[i][1] += a[i - 1][1];
    for(int i = 1; i <= n2; i ++) cin >> b[i][0] >> b[i][1], b[i][1] += b[i - 1][1];
 
    ll ans = 0;
    int id1 = 1, id2 = 1;
    while(id1 <= n1 && id2 <= n2){
        if(a[id1][0] == b[id2][0]){
            ans += min(b[id2][1], a[id1][1]) - max(b[id2 - 1][1], a[id1 - 1][1]);
            if(b[id2][1] > a[id1][1])
                id1 ++;
            else if(b[id2][1] < a[id1][1])
                id2 ++;
            else id1 ++, id2 ++;
        }
        else{
            if(b[id2][1] > a[id1][1])
                id1 ++;
            else if(b[id2][1] < a[id1][1])
                id2 ++;
            else id1 ++, id2 ++;
        }
    }
    cout << ans << endl;
}

F - Sugar Water 2

这个题,我只能说,我想写这个篇题解就是因为这个题
关于浮点数二分的讨论

题意:
两个人
分别有 N , M N,M N,M杯糖水
糖和水量分别是 a i , b i a_i,b_i ai,bi c i , d i c_i,d_i ci,di
这样就可以算每杯糖水的糖含量了( 100 x x + y % \frac{100x}{x + y}\% x+y100x%
这样两个人可以选两杯水混合,混合的可能总共有 N ∗ M N*M NM
问第 K K K大的浓度是多少
精确到小数点后9位

解题思路:
首先,我们知道了每杯水的含糖量和含水量,那么每杯水的糖浓度就是 100 x x + y % \frac{100x}{x + y}\% x+y100x%
所以,两杯水混合后的浓度就是 100 ( x 1 + x 2 ) ( x 1 + x 2 ) + ( y 1 + y 2 ) ① \frac{100(x_1+x_2)}{(x_1+x_2)+(y_1+y_2)}① (x1+x2)+(y1+y2)100(x1+x2)
直观地讲,当我们确定一个 x 1 x_1 x1的时候,当 y 2 y_2 y2不变, x 2 x_2 x2增加的时候,浓度一定增加

trick1:
假设我们的答案浓度为 k k k(小数),那么 k = x x + y k=\frac{x}{x+y} k=x+yx
所以 x = k 1 − k ∗ y x=\frac{k}{1-k}*y x=1kky
根据①式得, x = x 1 + x 2 x=x_1+x_2 x=x1+x2 y = y 1 + y 2 y=y_1+y_2 y=y1+y2
③④结合可知, ( x 1 − k 1 − k y 1 ) + ( x 2 − k 1 − k y 2 ) = 0 (x_1-\frac{k}{1-k}y_1)+(x_2-\frac{k}{1-k}y_2)=0 (x11kky1)+(x21kky2)=0
我们可以设 s 1 = x 1 − k 1 − k y 1 s_1=x_1-\frac{k}{1-k}y_1 s1=x11kky1 s 2 = x 2 − k 1 − k y 2 s_2=x_2-\frac{k}{1-k}y_2 s2=x21kky2
所以这样看就非常直观了
这个问题就可以优化为,已知 s 1 s_1 s1,找 s 2 s_2 s2,相加等于0
我们只需要枚举 x 1 x_1 x1,然后把 s 1 s_1 s1记录下来,然后在另一个数组里面找 s 2 s_2 s2就行

但是,现在我们知道的是:我们已知 k k k,找对应的 s 1   s 2 s_1\ s_2 s1 s2,还是没有解决第 K K K大问题
我们需要思考,首先我们确定了 s 1 s_1 s1 k k k,要让 k k k增大, x 2   y 2 x_2\ y_2 x2 y2需要满足什么条件
要让 k k k增加,首先需要让②等式中 x x x y y y的比例增加,反应在①~⑤中就是,⑤中的等式变成不等式
( x 1 − k 1 − k y 1 ) + ( x 2 − k 1 − k y 2 ) > 0 (x_1-\frac{k}{1-k}y_1)+(x_2-\frac{k}{1-k}y_2)>0 (x11kky1)+(x21kky2)>0

现在,我们得到了一个结论:当我们确定 s 1 s_1 s1 k k k时,要让 k k k增加,就是让 s 1 > − s 2 s_1>-s_2 s1>s2
所以,当我们确定 k k k的时候,我们可以把 s 1 s_1 s1求出来然后排序存放在数组 V V V中,再遍历另一个数组枚举 s 2 s_2 s2,在 V V V中二分出来 s 1 ≥ − s 2 s_1≥-s_2 s1s2的下标,把下标求和 s u m sum sum s u m sum sum就表示:对于所有的 s 1   s 2 s_1\ s_2 s1 s2,有多少种组合满足其浓度 k ′ < k k'<k k<k,这样第 K K K小问题就求出来了,第 K K K大问题就可以在每次求完下标的时候用枚举的数组长度 l e n len len减下标

然后就是我们经典的二分求 k k k的操作了
代码如下

const int N = 5e4 + 10;
long double a[N], b[N], c[N], d[N];
ll n, m, k;

int check(double mid){
    double x = mid / (1 - mid);
    vector<long double> v;
    for(int i = 1; i <= n; i ++)
        v.pb(a[i] - b[i] * x);
    sort(v.begin(), v.end());
    ll ans = 0;
    for(int i = 1; i <= m; i ++)
        ans += n - (lower_bound(v.begin(), v.end(), - (c[i] - d[i] * x)) - v.begin());
    if(ans >= k)
        return 0;
    return 1;
}

void solve(){
    cin >> n >> m >> k;
    for(int i = 1; i <= n; i ++) cin >> a[i] >> b[i];
    for(int i = 1; i <= m; i ++) cin >> c[i] >> d[i];

    long double l = 0, r = 1;
    while(fabs(l - r) > eps){
        long double mid = (l + r) / 2;
        if(check(mid))
            r = mid;
        else l = mid;
    }
    cout << l * 100 << endl;
}

trick2:
现在我们看到代码了
非常易懂,但是有个疑问,eps我们应该定义为多少
首先:for循环二分
先说一下正确性:我们求mid的过程是 ( l + r ) / 2 (l+r)/ 2 (l+r)/2所以求30次mid之后,即 2 − 30 < 1 0 − 9 2^{-30}<10^{-9} 230<109
通过这种方式可以保证误差小于1e-9
(实测,for40次能过35次会wa)

其次: e p s = 1 0 − 9 eps=10^{-9} eps=109
这个是错的
在这里插入图片描述
因为,此时我们精确到1e-9,但是输出的时候我们需要乘100,这样精确度就会达到1e-7了,显然精确度不够

最后: e p s = 1 0 − 11 eps=10^{-11} eps=1011
这样就可以解决上述的问题,但是需要long double才可以。
在这里插入图片描述

那么开始讨论正确的二分姿势:

关于for循环二分,我只能证明它的正确性,但是它依然是一个不确定的东西,不知道就在哪里奇奇怪怪地卡掉了
所以还得是eps
当我们需要精确到1e-13以内的话,都可以用long double解决二分精度问题,但是当需要更加精确的时候呢?

此时我们需要对整数部分和小数部分分别二分
①先二分整数部分,确定整数部分的值,这样的话,整数部分的精确度可以达到 2 126 2^{126} 2126(1e37左右)
②再对小数部分二分,我们小数部分变成整数,即乘个 1 0 k 10^{k} 10k,让小数部分变成整数,再进行二分操作,这小数部分的精度就可以达到 2 − 126 2^{-126} 2126(1e-37左右)

(G和Ex有时间再更,全是课,要似了)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值