ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Codeforces Round #789 (Div. 2)

2022-05-10 17:34:24  阅读:165  来源: 互联网

标签:int 789 long Codeforces vector 答案 Div yes define


Codeforces Round #789 (Div. 2)

B1 & B2

题意

给一个01串,要求其中连续的 \(1\) 或 \(0\) 长度都是偶数,保证串长为偶数。

每次可以翻转串中一个 \(0\) 或 \(1\) 的状态。

B1询问最小操作次数

B2 询问最小划分数。如,“11000111100”,的划分数就是4。

思路

B1 模拟也好贪心也好很好解决。

B2,贪心。

有一个分析的技巧,我们可以将一个连续段不断折半分割,最后的基础连续段就是 “11,00,10,01”,这四种,显然偶数段只可能由前两种组成,奇数段含总存在一个“01” 或 “10”。

将段划分为上面四种基础情况,就非常方便我们分析。

每次依次考虑连续的两个元素。如果是“10,01”我们总可以修改它让它和前面已经处理好的元素连接,也就是不会对划分段有贡献。而如果是“11,00”这样的段我们无法对其操作,就需要判断它的前一个元素是否相同,如果不同,这就是一个新连续段的起点,相应的就会令段数加一。

另外特判“0101”的情况,此时虽然都是“01”,的最终段数是1。

#include<bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;

void solve()
{    
    int N; cin >> N;
    string s; cin >> s;
    int cnt = 0, seg = 0;
    char c = ';';
    for(int i = 0;i < N;i += 2) {
        if(s[i] != s[i + 1]) cnt ++;
        else {
            if(c != s[i]) seg ++;
            c = s[i];
        }
    }
    cout << cnt << " " << max(1ll, seg) << "\n";
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

C

题意

给一个排列 \(p\),求四元组 \([a,b,c,d]\) 的数量

该四元组满足

  • \(a < b < c < d\)
  • \(p_a < p_c\) 且 \(p_b < p_d\)

最大接受时间复杂度:\(O(n^2logn)\)

思路

聪明的枚举。

我们枚举一对关系 \((a,c)\) 。然后计算 \([a + 1,c - 1]\) 范围中满足关系 \(p_b < p_d\) 的数量。

我们可以预处理出 \(f(b)\) ,表示满足关系 \(p_b < p_d\) 的数量。在枚举 \((a,c)\) 时用前缀和即可求得。将 \([a + 1,c - 1]\) 中的 \(f\) 加入答案。

但这么做就会将 \(d\) 在 \([a + 1,c - 1]\) 内部的不合法情况加入答案。因此要在枚举 \(c\) 的过程中维护 \(f\)

我们可以先枚举 \([1,c - 1]\) 中的 \(b\) 将所有 \(p_b < p_c\) 的情况减去(也就是 \(d\) 和 \(c\) 重合)。

  • 这样做就可以将所有在 \(d\) 在 \([a + 1,c - 1]\) 中的不合法情况减去吗?

答案是可以的。考虑枚举的基础情况,一定是 \(a\) 和 \(c\) 相差为 \(1\) ,此时这么做是合法的,当枚举进行,对任意一个大的区间都可以划分为前面的小区间上加上一个新的端点的情况,所以每次维护这个新加进来的区间端点即可。

(这好像也是常见讨论,以前见的一般是多次询问,每次询问更新的值很有限的这类题,可以先预暴力预处理答案,再在每次询问时维护答案。这个题则是先预处理答案,在枚举过程中对答案进行维护。)

#include<bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;

void solve()
{    
    int N; cin >> N;
    vector<int> a(N + 1);
    for(int i = 1;i <= N;i ++) cin >> a[i];
    vector<int> f(N + 1);// b>d
    for(int b = 1;b <= N;b ++)
        for(int d = b + 1;d <= N;d ++)
            f[b] += a[b] > a[d];
    
    int ans = 0;
    for(int i = 3;i <= N - 1;i ++) {// c
        for(int j = 2;j < i;j ++) f[j] -= a[j] > a[i];// b > c
        vector<int> pre(N + 1);
        for(int j = 1;j <= i;j ++) pre[j] = pre[j - 1] + f[j];
        for(int j = 1;j < i - 1;j ++) if(a[j] < a[i]) ans += pre[i - 1] - pre[j];
    }
    cout << ans << "\n";
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

D

题意

给定一个 \(N*M\) 的网格,一开始网格为空,现在将一个长度为 \(N*M\) 的01串依次插入网格 \((1,1)\) 位置,先前插入的数字将被这个新数挤开。

求每次插入后,网格中含 \(1\) 的行和列数之和。

思路

模拟,(DP?)

首先我们可以对行和列分开考虑,定义 \(f[i]\) 为考虑列,前 \(i\) 个数对答案的贡献。 \(g[i]\) 为考虑行,前 \(i\) 个数对答案的贡献。

考虑如何计算 \(f[i]\) ,将每一列看成整体对列分析,模拟可以发现,每次加入一个新数 \(s\),都是最后一列移到第一列并拼接在 \(s\) 的下面构成第一列。原来的 \([1 ,n-1]\) 列整体右移一位。只有当 \(s = 1\) 且最后一列贡献为0时,可以让答案加一,模拟这个过程即可。

考虑如何计算 \(g[i]\) ,思路仍然是如何从已知的结果转移,模拟发现,每加入一行,就相当于在原有基础上在第一行插入一行,答案是否变换取决于这个新行有没有 \(1\) ,因此可以写成 \(g[i] = g[i - M] + (last - i < M)\) 的计算式,其中 \(last\) 表示最后一个 $ 1$ 的位置。

特别的,如果只有一行,为防止越界,暴力即可。

#include<bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int MAXN =10 + 1e6 ,mod=1e9 + 7;

void solve()
{    
    int N,M; cin >> N >> M;
    string s; cin >> s;
    vector<int> f(N * M,0);
    // 列
    vector<bool> st(M);
    for(int i = 0;i < s.size();i ++) {
        int t = s[i] - '0';
        if(t && !st[i%M]) f[i] = f[i - 1] + 1, st[i%M] = 1;
        else f[i] = f[i - 1]; 
    }
    // 行
    int last = -1;
    vector<int> g(N * M,0);
    for(int i = 0;i < s.size();i ++) {
        int t = s[i] - '0';
        if(t) last = i;
        if(i < M) {
            if(last != -1) g[i] = 1;
        }else {
            if(i - last < M) g[i] = g[i - M] + 1;
            else g[i] = g[i - M];
        }
    }
    for(int i = 0;i < s.size();i ++) 
        cout << f[i] + g[i] << " \n"[i + 1 == s.size()];
    
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

E

题意

给两排列\(a~b\),构造一个新排列 \(p\) 令 \(a[i] = p[i]~且 ~b[j] = p[i]\) (其中 \(a[i] = b[j]\))

求重新分配排列值后 $\sum | a[i] - b[i] | $ 最大值

思路

贪心,结论题

对排列建边一定可以得到若干个环,我们可以贪心的将最大值和最小值凑成一对,依次给这些环相邻的结点分配。如,\([1,2,3,4,5,6]\),我们可以将 \((1,6)\) 凑成一对, \((2,5)\) 凑成一对,将这些数对分配给环上相邻结点即可。

可以发现,最终答案和环的大小无关,仅和环的奇偶性有关。

偶环时,按上面的贪心方案分配。

遇到奇环时,最后一个结点对答案 不提供贡献

因此暴力找环后,对每个环贪心即可。


建边是套路,之后就是打表找规律。就目前经验来看,对于环上分析,一般需要关注的是环的大小,环的奇偶性以及环上最后一个点这几个方面。

  • 尝试说明奇环上最后一个结点不影响答案这件事。

设一个大小为 \(n\) 的偶环对答案贡献为 \(s\) 。现插入一个结点 \(x\) 。环变成一个 \(n + 1\) 大小的奇环,最多影响其相邻两个点,设这两个点值为 \(a~b\) 。

则贡献从 \(|a - b|\) 变为 \(|a - x| +|b - x|\) 只有当 \(x\) 都大于或小于 \(a~b\) 时才会改变答案。考虑到贪心策略,这种情况一定不存在。

#include<bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;

void solve()
{    
    int N; cin >> N;
    vector<int> a(N + 1), b(N + 1);
    for(int i = 1;i <= N;i ++) cin >> a[i];    
    for(int i = 1;i <= N;i ++) cin >> b[i];
 
    vector<int> fa(N + 1);
    for(int i = 1;i <= N;i ++) fa[i] = i;
    function<int(int)> root = [&](int x) {
        return fa[x] == x ? x : (fa[x] = root(fa[x]));
    };
    auto unite = [&](int x,int y) {fa[root(x)] = root(y);};
 
    for(int i = 1;i <= N;i ++) {
        if(root(a[i]) != root(b[i])) unite(a[i],b[i]);
    }

    map<int,vector<int>> mp;
    for(int i = 1;i <= N;i ++) {
        mp[root(a[i])].push_back(a[i]);
    }

    vector<int> p(N + 1);
    for(int i = 1;i <= N;i ++) p[i] = i;
    int l = 1,r = N;
    int ans = 0;
    for(auto [f,v] : mp) {
        vector<int> t;
        for(int i = 1;i < v.size();i += 2) {
            t.push_back(p[l ++]);
            t.push_back(p[r --]);
        }
        int cur = 0;
        for(int i = 1;i < t.size();i ++) cur += abs(t[i] - t[i - 1]);
        if(t.size() > 1) cur += abs(t.front() - t.back());
        
        ans += cur;
    }
    cout << ans << endl;
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

标签:int,789,long,Codeforces,vector,答案,Div,yes,define
来源: https://www.cnblogs.com/Mxrush/p/16254421.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有