Educational Codeforces Round 95 [Rated for Div. 2]

Educational Codeforces Round 95 (Rated for Div. 2)

A

数学题

输入 a b k

题目上来只给你 一个s

两个操作
一个 s 可以换成 a 个 s
b 个 s 可以换 一个 c

一个 t = 一个 s + 一个 c(该步骤不算操作)

问要 k 个 t 需要 多少 次操作

有点像进制转化,昨天一直wa是因为 k * b 个 s 变成 k 个 c 是 k 步,而不是 b 步,

还有就是不是每次 + a, 是每次 + (a - 1),最后虽然说看出来了,可是太慢了

队友之间打比赛突然发现是 1 + 1 + 1 < 1 的太难了,互相讨论,然后自己还被打扰,思路断了就想不出来了,哎,难受,互相之间的理解还是不够,一起打的比赛太少了

我一直说 k * b 个 s 变成 k 个 c 是 b 步,没有人说我是错的,哎

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int main(){
    int t;
    scanf("%d",&t);
    while(t --){
        long long a, b, k;
        scanf("%lld%lld%lld",&a,&b,&k);

        long long ans = k;

        long long l = (b+1)*k - 1;

        ans += l/(a-1);

        if(l % (a - 1) != 0)
            ans ++;

        printf("%lld\n",ans);
    }
    return 0;
}

B

给两个数组,
数组A是一个正常的数组
数组B是一个由零一组成的数组,

数组B中为零的位置对应于数组A的位置是可以进行交换的,
数组B中为一的位置对应于数组A的位置是不能动的

设 k 为数组A变化完的数组的前缀和数组的最后一个为负数的点的位置
如果前缀和中没有为负数的点,那么k = 0,

找到 k 的最小值

麻烦的地方在于有一些点是锁住的,

应该是把该数组的可动位置从大到小排序,

问的是前缀和数组 < 0 的最后一个位置 的 最小值

因为是前缀和数组,如果是从小到大的话,
先是一堆负数,前缀和数组的负数特别大,然后慢慢加正数,负数慢慢接近0,这样k是接近数组结尾的

而如果是从大到小的话
先是一些正数,前缀和也是正的,在是一些负数,前缀和数组的值慢慢变小,有可能没有负值,那么k = 0,

其实如果所有数加一起是负数的话,按照原数组相加就行了,因为无论怎么变换顺序,前缀和数组的最后一项也是负数,

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
#define Inf 1000000000000000
int main(){
	int t;
	cin>>t;
	while(t--){
		int n;
		cin>>n;
		vector<int> a(n),l(n);
		long long all = 0;
		for(int i = 0;i < n;i ++) scanf("%d",&a[i]), all += a[i];
		for(int i = 0;i < n;i ++) scanf("%d",&l[i]);

        if(all < 0){//可有可无
            for(int i = 0;i < n;i ++){
                if(i!=0)cout<<' ';
                cout<<a[i];
            }
            cout<<endl;
            continue;
        }

		vector<int> L;
		for(int i = 0;i < n;i ++){
			if(l[i]==0){
				L.push_back(a[i]);
			}
		}
		sort(L.begin(),L.end());

		for(int i = 0;i < n;i ++){
			if(l[i]==0){
				a[i] = L.back();
				L.pop_back();
			}
		}

		for(int i = 0;i < n;i ++){
			if(i!=0)cout<<' ';
			cout<<a[i];
		}
		cout<<endl;

	}

    return 0;
}

C

其实c还挺麻烦的

如果for一遍判断01去模拟的话是不对的,情况太多了,无法讨论完
比如几个数据
0 0 0 1 1
0 1 0 0 0 1 1
0 1 0 1 0 1 1
1 0 0 0 1 1

可是其实可以简化
除第一位后如果出现了三个连续的1那么必然会 cnt ++
其他的都不会出现 cnt 增加的情况

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

int n;
int z[200010];

int main(){
    int _;
    scanf("%d",&_);
    while(_--){

        scanf("%d",&n);
        for(int i = 0;i < n;i ++)
            scanf("%d",&z[i]);

        int all = 0;
        int t = 0;
        if(z[0])
            all ++;

        for(int i = 1;i < n;i ++){
            if(z[i] && z[i + 1] && z[i + 2] && (i + 2) < n ){
                all ++;
                i += 2;
            }
        }
        printf("%d\n",all);
    }
    return 0;
}

还有一种方法就是DP

其实DP的方向还是挺容易想到的,

因为没有特判,要求是按照规定进行找到最值,中间是连续的

可是转移方程怎们写呢

两个数组互相之间更新
一个代表自己 (m) 的数组,下标表示该位置是自己时朋友花费最少的值,
一个代表朋友 ( f ) 的数组,下标表示该位置是朋友时朋友花费最少的值,

然后用f[i]就可以更新m[i + 1]和m[i + 2],用m[i + 1]就可以更新f[i + 2]和f[i + 3],用m[i + 2]就可以更新f[i + 3]和f[i + 4]。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 200200;

int n;
int dp[N][2];
int z[N];

int main(){
    int _;
    scanf("%d",&_);
    while(_--){
        memset(dp, 0x3f3f3f, sizeof dp);

        scanf("%d",&n);
        for(int i = 0;i < n;i ++) scanf("%d",&z[i]);

        if(z[0]){
            dp[0][0] = 1;
            dp[1][1] = 1;
            dp[2][1] = 1;
        }
        else{
            dp[0][0] = 0;
            dp[1][1] = 0;
            dp[2][1] = 0;
        }

        if(z[1]){
            dp[1][0] = dp[0][0] + 1;
            dp[3][1] = min(dp[1][0], dp[3][1]);
            dp[2][1] = min(dp[1][0], dp[2][1]);
        }
        else{
            dp[1][0] = dp[0][0];
            dp[3][1] = dp[1][0];
            dp[2][1] = min(dp[1][0], dp[2][1]);
        }

        for(int i = 1;i < n;i ++){
            dp[i + 1][0] = min(dp[i + 1][0], dp[i][1] + z[i + 1]);
            dp[i + 2][0] = min(dp[i + 2][0], dp[i][1] + z[i + 1] + z[i + 2]);
            dp[i + 1][1] = min(dp[i + 1][1], dp[i][0]);
            dp[i + 2][1] = min(dp[i + 2][1], dp[i][0]);
        }

        printf("%d\n",min(dp[n-1][0], dp[n-1][1]));
    }
    return 0;
}

D
上来一发T

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
using namespace std;
 
int n, m;
set <int> s;
set <int> ::iterator it;
set <int> ::iterator itr;
set <int> ::iterator ite;
 
int main(){
    int l;
    scanf("%d%d", &n, &m);
    for(int i = 0;i < n;i ++)
        scanf("%d", &l), s.insert(l);
 
    it = s.begin();
    ite = s.end();
    ite --;
    int ans = *ite - *it;
    itr = it;
    itr ++;
    for( ; it != ite ; it ++, itr ++){
        ans = min(ans, *it - *s.begin() + *ite - *itr);
    }
    printf("%d\n",ans);
    
    while(m --){
        int a, b;
        scanf("%d%d",&a,&b);
        if(a) s.insert(b);
        else s.erase(b);
 
        if(s.begin() == s.end()){
            printf("0\n");
            continue;
        }
 
        it = s.begin();
        ite = s.end();
        ite --;
        int ans = *ite - *it;
        itr = it;
        itr ++;
        for( ; it != ite ; it ++, itr ++){
            ans = min(ans, *it - *s.begin() + *ite - *itr);
        }
        printf("%d\n",ans);
    }
 
    return 0;
}

然后想可以优化为找到中值,然后继续左右相减,可是不对
比如 1 2 4 6 8 … 50 52 100
其中52 应该是向左走,而不是向右走
想错了

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
using namespace std;

int n, m;
set <int> s;
set <int> ::iterator it;
set <int> ::iterator it2;
set <int> ::iterator it2r;
set <int> ::iterator ite;

int main(){
    int l;
    scanf("%d%d", &n, &m);
    for(int i = 0;i < n;i ++)
        scanf("%d", &l), s.insert(l);

    it = s.begin();
    ite = s.end(), ite --;

    int ans = *ite - *it;
    it2 = s.lower_bound((*ite - *it + 1) / 2);
    it2 --;
    it2r = ++ it2;
    it2 --;
    ans = min(ans, *it2 - *it + *ite - *it2r);
    printf("$$$$$$ %d\n",ans);
    
    while(m --){
        int a, b;
        scanf("%d%d",&a,&b);
        if(a) s.insert(b);
        else s.erase(b);

        if(s.begin() == s.end()){
            printf("0\n");
            continue;
        }

        it = s.begin();
        ite = s.end(), ite --;

        int ans = *ite - *it;
        it2 = s.lower_bound((*ite - *it + 1) / 2);
        it2 --;
        it2r = ++ it2;
        it2 --; 
        ans = min(ans, *it2 - *it + *ite - *it2r);
        printf("$$$$$$ %d\n",ans);
    }

    return 0;
}

也就是顺便看了一篇二分函数的博客

multiset 的 删除操作:

ms.erase(2);//把所有的 2 删除
ms.erase(ms.find(2));//只删除一个 2

其实这个的思路巧妙,代码也不好写,这个题还是挺有难度的

代码重写一次,还改过无数次,分类讨论的各个分支,输入条件的边界情况

得亏知道思路后打了一下,没想到这么麻烦

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <set>
using namespace std;

int n, m;

int main(){
    set <int> s;
    set <int> :: iterator se;
    set <int> :: iterator sb;
    set <int> :: iterator sl;
    set <int> :: iterator sr;

    multiset <int> ms;
    multiset <int> :: iterator mse;
    multiset <int> :: iterator msb;
    multiset <int> :: iterator mst;
    scanf("%d%d", &n, &m);
    for(int i = 0 ; i < n;i ++){
        int l;
        scanf("%d",&l);
        s.insert(l);
    }

    if(n == 1)//得特判,没有 mse
        printf("0\n");
        
    else{
        sb = s.begin();
        se = s.end();
        sr = sb, sr ++;
        for(sl = sb;sr != se;sl ++, sr ++){
            ms.insert(*sr - *sl);
        }
        se --;
        mse = ms.end(), mse --;
        int ans = *se - *sb - *mse;
        printf("%d\n",ans);
    }

    while(m --){
        int a, b;
        scanf("%d%d",&a,&b);
        if(a){
            int sll = s.size();
            if(sll == 0){
                s.insert(b);
            }
            else if(sll == 1){
                if(*s.begin() > b) ms.insert(*s.begin() - b);
                else ms.insert(b - *s.begin());
                s.insert(b);
            }
            else{
                sb = s.begin();
                se = s.end(), se --;
                if(b > *sb && b < *se){
                    sr = s.upper_bound(b);
                    sl = sr;
                    sl --;
                    ms.erase(ms.find(*sr - *sl));//只删除一个,后面也一样
                    ms.insert(*sr - b);
                    ms.insert(b - *sl);
                    s.insert(b);
                }
                else if(b <= *sb){
                    ms.insert(*sb - b);
                    s.insert(b);
                }
                else{
                    ms.insert(b - *se);
                    s.insert(b);
                }
            }
        }
        else{
            int sll = s.size();
            if(sll == 1 || sll == 2){
                s.erase(b);
                ms.clear();
            }
            else{
                sb = s.begin();
                se = s.end(), se --;
                if(*se == b){
                    s.erase(b);
                    sl = s.end();
                    sl --;
                    mst = ms.find(b - *sl);
                    ms.erase(mst);
                }
                else if(*sb == b){
                    s.erase(b);
                    sl = s.begin();
                    mst = ms.find(*sl - b);
                    ms.erase(mst);
                }
                else{ 
                    s.erase(b);
                    sr = s.upper_bound(b);
                    sl = sr;
                    sl --;
                    ms.insert(*sr - *sl);    
                    mst = ms.find(*sr - b);
                    ms.erase(mst);   
                    mst = ms.find(b - *sl);
                    ms.erase(mst);
                }
            }
        }
        if(s.size() <= 2){
            printf("0\n");
            continue;
        }
        sb = s.begin();
        se = s.end(), se --;
        mse = ms.end(), mse --;
        int ans = *se - *sb - *mse;
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值