Codeforces Round #582 (Div. 3) 全题解

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



A. Chips Moving

解题心得:统计一下奇数更少还是偶数更少,输出少的那个的个数就行了。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+1100;

int n, num[maxn];

int main() {
    int cnt1, cnt2;//记录奇数个数和偶数个数
    cnt1 = cnt2 = 0;
    scanf("%d", &n);
    for(int i=1;i<=n;i++) {
        scanf("%d", &num[i]);
        if(num[i]&1) cnt1++;
        else cnt2++;
    }
    printf("%d\n", min(cnt1, cnt2));
}


B. Bad Prices

题意:现在有一个商品,总共有 n n n天,商品每一天有一个价格,如果今天价格位 T i Ti Ti,但是如果在之后有一个价格小于 T i Ti Ti,则今天为bad day,问n天中有多少个bad day。

解题心得:直接从 n n n天开始往前更新最低价格,如果当前价格有大于最低价格的则答案加一,否则更新最低价格。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+1100;

int n, num[maxn];

int main() {
    int t; scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) scanf("%d", &num[i]);
        int cnt = 0, Min = INT_MAX;
        for (int i = n; i >= 1; i--) {
            if (num[i] <= Min) Min = num[i];
            else cnt++;
        }
        printf("%d\n", cnt);
    }
    return 0;
}


C. Book Reading

题意:现在有一本书 n n n页,每次可以看 m m m页直到看完整本书,当每一个 m m m p i p_{i} pi倍有一个 k i = ( m ∗ p i ) % 10 k_{i}=(m*p_{i})\%10 ki=(mpi)%10,现在你需要的到 ∑ k i \sum k_{i} ki

解题心得:直接找一个循环节为 10 10 10 k i k_{i} ki的每一个值,然后每一个所有能够得到完整循环节直接算出来,不能得到的单个加一下。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+1100;

vector <ll> ve;//存储循环节中的每一个数

int main() {
    //    freopen("1.in.txt", "r", stdin);
    int t; scanf("%d", &t);
    while(t--) {
        ll n, m; scanf("%lld%lld", &n, &m);
        if(m > n) {
            puts("0");
            continue;
        }
        
        ve.clear();
        ll cnt = n/m;//cnt个完成的循环节
        ll sum = 0;//一个循环节的和
        for(int i=1;i<=10;i++) {
            ve.push_back((m*i)%10);
            sum += (m*i)%10;
        }

        ll ans = cnt/10*sum;
        cnt = cnt%10;//剩下的单独算一下就行了
        for(int i=0;i<cnt;i++)
            ans += ve[i];
        printf("%lld\n", ans);
    }
    return 0;
}


D2. Equalizing by Division (hard version)

题意:现在有一个长度为 n n n的数列,你可以将数列中的每一个多次数除以 2 2 2并且向下取整,每当一个数除以 2 2 2并下向下取整看作一个操作,问你至少需要多少个操作可以让数列中至少有 k k k个数相同。

解题心得:首先将整个数列按升序排列,然后从小到大预处理出不大于 x ( x ∈ [ 1 , n ] ) x(x \in [1,n]) x(x[1,n])的数的个数。然后从 1 1 1开始枚举每一个数作为满足 k k k个数的数答案,假设枚举当前的数为 z z z,则可能得到 z z z的数为 [ z ∗ 2 , z ∗ 2 + 1 ] , [ z ∗ 2 ∗ 2 , ( z ∗ 2 + 1 ) ∗ 2 + 1 ] . . . [z*2, z*2+1], [z*2*2, (z*2+1)*2+1]... [z2,z2+1],[z22,(z2+1)2+1]...,统计一下每段区间内可以在数列中找到的个数,由于之前已经预处理出来,寻找个数的复杂度就是 O ( 1 ) O(1) O(1),枚举加上每次按照一个类似倍增的寻找复杂度总共是 O ( n ∗ l o g n ) O(n*logn) O(nlogn)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5+1100;

int n, k, num[maxn], sum[maxn];//sum[i]表示不大与i的数有多少个,这里用的是树状数组计数

int lowbit(int x) {
    return x&-x;
}

void add(int x) {
    if(x <= 0) return ;
    while(x < maxn) {
        sum[x]++;
        x += lowbit(x);
    }
}

int get_sum(int x) {
    if(x <= 0) return 0;
    int Sum = 0;
    while(x) {
        Sum += sum[x];
        x -= lowbit(x);
    }
    return Sum;
}

int main() {
    //    freopen("1.in.txt", "r", stdin);
    scanf("%d%d",&n, &k);
    int ans = 0;
    for(int i=1;i<=n;i++) {
        scanf("%d", &num[i]);
        add(num[i]);
        int z = num[i];
        while(z) {//统计若最终的答案数字为0需要的次数
            ans++;
            z /= 2;
        }
    }
    sort(num+1, num+1+n);
    for(int i=1;i<=num[n];i++) {
        int cnt = get_sum(i) - get_sum(i-1);
        int need = k - cnt, p = 1;
        if(need <= 0) {
            ans = 0;
            break;
        }
        
        int l = i*2, r = i*2+1, temp = 0;//类似倍增每一个区间
        while(l <= num[n]) {
            ll now = get_sum(r) - get_sum(l-1);
            if(now < need) {
                temp += now*p;
                need -= now;
            } else {
                temp += need*p;
                need = 0;
                break;
            }
            l = l*2;
            r = r*2+1;
            p++;
        }
        if(need == 0) ans = min(ans, temp);
    }
    printf("%d\n", ans);
    return 0;
}


E. Two Small Strings

解题心得:其实就是将 a b c abc abc的全排列然后凑成 3 ∗ n 3*n 3n的长度,和分别将 a b c abc abc放在一起调换位置就是所有的情况,然后将所有结果再检验一下就行了,按情况来说不可能有 N O NO NO。直接莽一波,单车变摩托。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000;
string tot = "abc", x1, x2;

vector <string> ve;
int n;

void get_string() {
    do{
        string temp;
        for(int i=0;i<n;i++) temp += tot;
        ve.push_back(temp);
        temp.clear();
        for(int i=0;i<3;i++) {
            for(int j=0;j<n;j++) temp += tot[i];
        }
        ve.push_back(temp);
    } while(next_permutation(tot.begin(), tot.end()));
}

int main() {
    //    freopen("1.in.txt", "r", stdin);
    scanf("%d", &n);
    cin>>x1>>x2;
    get_string();

    for(int i=0;i<ve.size();i++) {
        string now = ve[i];
        if(now.find(x1) == -1 && now.find(x2) == -1) {
            puts("YES");
            cout<<now;
            return 0;
        }
    }
    puts("NO");
    return 0;
}


F. Unstable String Sort

解题心得:

  • 假设在第一个数列中 s [ 3 ] &lt; = s [ 5 ] s[3]&lt;=s[5] s[3]<=s[5],但在第二个数列中 s [ 5 ] &lt; = s [ 3 ] s[5]&lt;=s[3] s[5]<=s[3],这就可以直接得到 s [ 5 ] = s [ 3 ] s[5]=s[3] s[5]=s[3],这个时候若是 5 5 5 3 3 3在数列中的位置不相邻,则从 5 5 5的位置到 3 3 3的位置这段区间内的所有 s [ i ] s[i] s[i]都相等。
  • 发现了以上规律这个题就有思路了,肯定是先相互对比第一个数列和第二个数列,将 s [ i ] s[i] s[i]相等的区间全找出来,这里可以用并查集操作,若两个区间有相交的部分,那么这两个区间合并成一个区间,最后看有多少个区间,若 m m m大于区间个数肯定是输出 N O NO NO,若小于了区间个数则将相邻的区间合并一下,最后变成m个区间就行了。
  • 但是这个题对于位置和数值都非常的绕,注意不要写飘了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 3e5+100;

int pos[maxn], n, m, num1[maxn], num2[maxn], father[maxn], Max[maxn], ans[maxn];
bool vis[maxn];
vector <int> ve[maxn];
void init() {
    scanf("%d%d", &n, &m);
    for(int i=1;i<=n;i++) {
        scanf("%d", &num1[i]);
        pos[num1[i]] = i;
        father[i] = i;
        Max[i] = i;
    }
    for(int i=1;i<=n;i++) scanf("%d", &num2[i]);
}

int find(int x) {
    if(x == father[x]) return x;
    return father[x] = find(father[x]);
}

void merge(int x,int y) {
    int fx = find(x);
    int fy = find(y);
    if(fx != fy) {
        father[fy] = father[fx];
    }
}

void merge_all() {
    int tail;
    for(int i=1;i<=n;i=tail+1) {
        int fi = find(i);
        tail = Max[fi];
        for(int j=i+1;j<=tail;j++) {
            int fx = find(j);
            if(fx != fi) {
                tail = max(tail, Max[fx]);
                merge(i, j);
            }
        }
    }
}

int main() {
    //    freopen("1.in.txt", "r", stdin);
    init();
    for(int i=1;i<=n;i++) {
        //找到相同字符的区间,这里只需要记录一下区间右端点的值就行了
        int pos1 = pos[num1[i]], pos2 = pos[num2[i]];
        int Max1 = Max[find(pos1)], Max2 = Max[find(pos2)];
        merge(pos1, pos2);
        Max[find(pos1)] = max(Max1, Max2);
    }

    set <int> se;

    merge_all();//将区间有交集的合并

    for(int i=1;i<=n;i++) {//记录有多少个区间
        int fi = find(i);
        se.insert(fi);
        ve[fi].push_back(num1[i]);
    }
    
    if(se.size() < m) {
        puts("NO");
        return 0;
    }
    puts("YES");

    int Index = 'a'-1;//当m小于区间个数的时候要合并一些相邻的区间
    for(int i=1;i<=n;i++) {
        int fi = find(i);
        if(!vis[fi]) {
            if(Index < m+'a'-1)
                Index++;
            vis[fi] = true;
            for(int j=0;j<ve[fi].size();j++) {
                int va = ve[fi][j];
                ans[va] = Index;
            }
        }
    }

    for(int i=1;i<=n;i++) printf("%c", ans[i]);
    return 0;
}


G. Path Queries

题意:现在有一棵树,树上每个边有个权值,现在有 m m m次操作,每次操作分别是将边权大于等于 q q q的边删去,问还剩多少条路径。

解题心得:删边是不可能删边的,肯定是离线将询问全部记录下来,然后从小到大将边加进去,用并查集维护并且记录一下每个并查集内有多少个点,然后一个不停加入边合并累加值的过程。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 3e5+100;

struct Edge {
    ll u, v, va;
    bool operator < (const Edge &x) const {
        return va < x.va;
    }
}edge[maxn];//记录每一条边,用于并查集操作
ll n, m, father[maxn], cnt[maxn];
ll ans[maxn];

struct Query {//记录每一个询问用于离散化
    ll pos, va;
    bool operator < (const Query& x) const {
        return va < x.va;
    }
}q[maxn];

void init() {
    scanf("%lld%lld",&n, &m);
    for(ll i=0;i<=n+1;i++) {
        father[i] = i;
        cnt[i] = 1;
    }
    for(ll i=1;i<n;i++) {
        ll a, b, c;scanf("%lld%lld%lld", &a, &b, &c);
        edge[i] = {a, b, c};
    }

    sort(edge+1, edge+n);
    for(ll i=1;i<=m;i++) {
        scanf("%lld",&q[i].va);
        q[i].pos = i;
    }
    sort(q+1, q+1+m);
}

ll find(ll x) {
    if(x == father[x]) return x;
    return father[x] = find(father[x]);
}

void merge(ll x, ll y) {
    ll fx = find(x);
    ll fy = find(y);

    cnt[father[fy]] += cnt[father[fx]];
    father[fx] = father[fy];
}

int main() {
//    freopen("1.in.txt", "r", stdin);
    init();
    ll Ans = 0;//记录累加值
    ll Index = 1;
    for(ll i=1;i<=m;i++) {//将每个询问和上一个询问差值中间的边加入
        while(Index <= (n-1) && edge[Index].va <= q[i].va) {
            Edge now = edge[Index];
            ll fu = find(now.u), fv = find(now.v);
            if(fu != fv) {
                Ans += cnt[fu] * cnt[fv];
                merge(now.u, now.v);
            }
            Index++;
        }
        ans[q[i].pos] = Ans;
    }
    for(ll i=1;i<=m;i++) printf("%lld ", ans[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值