ninth

 

A - Kids Seating

CodeForces - 1443A

 

题意:

n个数,大小范围不超过4n,他们必须要满足:

任意两个数的最大公约数不能是1

任意一个数不能被另一个数整除

 

思路:

由于题目描述有 If there are multiple answers, print any of them. 这样的题一般都是直接输出。如果要满足题意的两个条件,可以想到输出的这一串数必须全都是偶数,这个题从前往后是不如从后往前好想的,所以我们直接从4n倒着依次输出。而从4n开始,第一次输出4n,第二次输出4n-2,在输出4n-4 ··· ··· , 所以输出n个数之后,输出的最小的那个数为2n+2,比4n的一半2n还要大2,所以一定不存在可以被整除的情况。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        int n;
        cin >> n;
        for (int i = 4 * n; i > 2 * n; i  -= 2)
            cout << i << ' ';
        cout << endl;
    }
    return 0;
}

 

B - Saving the City

 CodeForces - 1443B 

题目翻译:
Bertown是一个由n个建筑组成的城市,且这n个城市成一条直线。
城市的保安部门发现一些建筑下面有炸弹。有一张地图可以显示哪些建筑下面有炸弹,该地图是一个长度为n的字符串,如果第 i 个字符是 “1” ,这表示第 i 个建筑下面有炸弹,如果是 “0” ,表示第 i 个建筑下面没有炸弹。
Bertown最厉害的士兵知道怎么在不毁坏建筑的前提下引爆炸弹。当第x个建筑下面的炸弹被引爆时,将会引爆相邻的两个建筑(x - 1和x + 1)下面的炸弹(如果相邻的建筑下面没有炸弹,就不会发生什么)。因此,只要引爆一段连续的炸弹中的任意一个炸弹,就足以引爆这一整段连续的炸弹。引爆一颗炸弹,需要花费a个硬币,你可以执行任意多次这样的操作。
你也可以在一个没有炸弹的建筑下面放置一个炸弹,这需要花费b个硬币,你也可以执行任意多次这样的操作。
你可以以任意顺序执行上面的操作。
你想要引爆所有的炸弹,计算最少需要花费多少硬币。

解题思路:
对于一段连续的炸弹,有两种选择:直接引爆或者连接别的炸弹,再一起爆炸。比如“00111000110”,a=5,b=1时,如果两段炸弹各自引爆,需要花费5+5=10个硬币。如果连接后再引爆,需要花费3+5=8个硬币,显然后者的选择更省钱。因此可以得出结论,如果两段炸弹之间的距离*b≤a,则连接后引爆,否则直接引爆。
我们可以指定引爆第一段连续的炸弹,而第二段炸弹如何引爆取决于其与前一段炸弹之间的距离,之后同样的道理。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

int a, b , res;
string s;

void solve()
{
    cin >> a >> b;
    cin >> s;
    res = 0;
    int flag = 0, len = 0;//flag表示第一次遇到1的情况
    for (int i = 0; i < s.size(); i++)
    {
        if (s[i] == '1')
        {
            if (flag == 0)//如果是第一次遇到1,就直接加上a
            {
                flag = 1;//之后就不是第一次遇到1了
                res += a;
            }
            else//之后再遇到1的情况
            {   //继上一段的爆炸,看看是把这一段直接引爆还是连接两端之间距离
                res += min(a, len * b);
            }
            len = 0;//这一段引爆后len变为0 看下一段
        }
        if (s[i] == '0') len++;
    }
    cout << res << endl;
    return ;
}

int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

 

C - The Delivery Dilemma

 CodeForces - 1443C 

题意:

n份菜,可以选择饭店送,也可以自己拿,求总的花费时间最小

思路:

方法一:二分,定下上界是全部菜自己去拿,在此基础上二分,如果当前菜的外卖时间<mid,就让他送,不必花费时间,但是如果>mid,就要自己去拿,算出自己去拿的时间,和mid比较。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

int a[200010], b[200010];
int t, n;

bool check(long long mid)
{
    long long s = 0;
    for (int i = 0; i < n; i++)
        if (a[i] > mid)
            s += b[i];
    return mid >= s;
}

int main()
{
    cin >> t;
    while (t--)
    {
        cin >> n;
        long long l = 0, r = 0;
        for (int i = 0; i < n; i++) cin >> a[i];
        for (int i = 0; i < n; i++)
        {
            cin >> b[i];
            r += b[i];
        }

        while (l < r)
        {
            long long mid = l + r >> 1;
            if (check(mid)) r = mid;
            else l = mid + 1;
        }
        cout << r << endl;
    }
    return 0;
}

 

顺便总结一下关于二分模板及向上取整还是向下取整的问题:

两个模板的区别在于mid的取整(向上还是向下)
即mid =  l + r >> 1 还是 mid =  l + r + 1 >> 1;
取决于后两行的写法,而后两行的写法取决于题目分析的过程

注意:关于上取整还是下取整:
如果题目中要求最大值,就用上取整,如果要求最小值,就用下取整
模板一:(向下取整)
while(l < r) { int mid = l + r >> 1; if(check(mid)) r = mid;//右边界向下 此时mid向下取整(记忆方法) else l = mid + 1; } 模板二:(向上取整) while(l < r) { int mid = l + r + 1 >> 2; if(check(mid)) l = mid;//左边界向上 此时mid向上取整(记忆方法) else r = mid - 1; }

 

方法二:前缀和,直接枚举哪些需要配送

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long ll;

const int maxn = 2e5 + 10;

struct Node {
    int a, b;
}a[maxn];
ll sum[maxn];

bool cmp(Node a, Node b) {
    return a.a < b.a;
}

int main() {
    int T; 
    cin >> T;
    while (T--) {
        int n; 
        cin >> n;
        for (int i = 1; i <= n; i++) cin >> a[i].a;
        for (int i = 1; i <= n; i++) cin >> a[i].b;
        sort(a + 1, a + 1 + n, cmp);

        for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i].b;
    
        ll ans = sum[n];
        for (int i = 1; i <= n; i++)
            ans = min(ans, max(sum[n] - sum[i], 1ll * a[i].a));

        cout << ans << endl;
    }
    return 0;
}

方法三:贪心 + 优先队列

首先,如果配送时间少于自己取的时间,则肯定选择配送
其次,第一步中的配送时间中有一个最大值 M, 而所有配送时间少于 M 的店家都可以选择配送, 将剩下的
配送时间丢进一个小根堆里
最后,把所有剩下的店家自己取的时间加起来得到 SUM, 如果 SUM 比小根堆的堆顶大,则选择堆顶的店家配送,
一定比当前更优,然后更新一下最大时间,直到 SUM 小于堆顶,此时即为最优答案

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<stack>
#include<queue>
using namespace std;
typedef long long ll;
typedef pair<ll, int> P;

const int maxn = 200010;

int T, n;
ll a[maxn], b[maxn]; 

priority_queue<P, vector<P>, greater<P> > Q;

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

ll read() 
{ 
    ll s = 0, f = 1; 
    char ch = getchar(); 
    while (ch < '0' || ch > '9') 
    {    
        if (ch == '-') f = -1; 
        ch = getchar();
    } 
    while (ch >= '0' && ch <= '9') 
    { 
        s = s * 10 + ch - '0';
        ch = getchar();
    }
    return s * f; 
}


int main(){
    T = read();
    while(T--){
        while(!Q.empty()) Q.pop();
        n = read();
        for(int i = 1 ; i <= n ; ++i) b[i] = read();
        for(int i = 1 ; i <= n ; ++i) a[i] = read();
        
        ll suma = 0, sumb = 0, time = 0;
        for(int i = 1 ; i <= n ; ++i){
            if(a[i] >= b[i]) time = max(time, b[i]);
        }
        
        for(int i = 1 ; i <= n ; ++i){
            if(b[i] > time){
                suma += a[i];
                Q.push(make_pair(b[i], i));
            }
        }
        
        if(time >= suma){
            printf("%lld\n", time);
        } else{
            while(!Q.empty()){
                P p = Q.top();
                if(suma >= p.first){
                    time = max(time, p.first);
                    suma -= a[p.second];
                    Q.pop();
                } else break;
            }

            time = max(time, suma);
            printf("%lld\n", time);
        }
    }
    return 0;
}

D - Extreme Subtraction

 CodeForces - 1443D 

题目大意:
有一个长度为n的数组a。你可以进行无数次如下操作:

1.a1~ai 减1
2.ai~an减1
问能否使数组中的元素全部变成0;

思路:
转化成一个差分问题。(假设差分数组为ans)要使数组中的全部数都为0,那么差分数组也必须为0且ans[1]=0。
那么我们来看两种操作对于差分数组有何影响:

操作1:ans[1]-1 且ans[i+1]+1。那么我们就可以凭借操作1,将差分数组中的负数变成0,同时减小ans[1]。

操作2:ans[i+1]-1且ans[n+1]+1。那么我们就可以凭借操作2,将差分数组中的正数变成0,而ans[n+1]为多少和我们并没有关系。

在最开始时ans[1]=a[1],所以只要差分数组的负数和的绝对值小于等于a[1],即输出YES,否则输出NO。
因为如果差分数组的负数和的绝对值大于a[1],那么要使后面的数都变成0,ans[1]就会变成负数,而并没有使ans[1]由负数变成0的操作,操作2只能使ans[1]由正数变成0,所以这样并不满足要求。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 3e4 + 10;

int a[N];
int ans[N];

int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        int n;
        cin >> n;
        a[0] = 0;
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i];
            ans[i] = a[i] - a[i - 1];
        }
        int res = 0;
        for (int i = 2; i <= n; i++)
            if (ans[i] < 0) 
                res = res + (-1) * ans[i];

        if (res <= a[1]) puts("YES");
        else puts("NO");
    }
    return 0;
}

 

posted @ 2021-03-12 10:04  彦辰kkkkk  阅读(224)  评论(0)    收藏  举报