Codeforces补题2.0

前记:最近vp好几场cf感觉手感越来越差,div2的D题是怎么也开不了,人麻了已经。

Codeforces Round 879 (Div. 2)

D. Survey in Class

题意:有 n 个同学,m 个主题,第 i 个人准备了主题编号区间是 l[i] \sim r[i] ,你可以选择问一些主题,对于你问的这些主题,若学生 i 准备了,则他的手往上举 1 ,否则往下放 1 (可以小于 0),问你选择哪些问题,可以使得举得最高的人和举的最低的人差值最大。

题解:

不难发现对于两个不同的同学 i,j  ,他们之间的最大差值就是他们不相交的区间长度的最大值乘 2 ,例:对于区间 \left [ 1,5 \right ] 和 \left [ 3,9 \right ] 来说,它们不相交的区间分别是 \left [ 1,2 \right ] 和 \left [ 6,9 \right ] ,因此最大区间长度是 4 ,那么它们之间的最大差值是 4 \times 2=8 。

于是我们的思路是依次遍历所有区间,求可以和当前区间 i 相交使得区间 i 不相交的区间长度最大的区间长度值。

继续我们发现对于两个区间 i,j 有四种关系:

  1. 区间 i 从左端点与区间 j 有交集
  2. 区间 i 从右端点与区间 j 有交集
  3. 区间 i 完全包含区间 j
  4. 区间 i 和区间 j 不相交

对于第一种情况,我们可以贪心地发现,区间 i 如果和具有区间右端点最小的区间相交,对于区间 i 一定可以得到不相交的区间长度最大。

对于第二种情况,我们可以贪心地发现,区间 i 如果和具有区间左端点最大的区间相交,对于区间 i 一定可以得到不相交的区间长度最大。

对于第三种情况,我们可以认定长度最小的那条区间被区间 i 完全包含,假设这区间真的被区间 i 包含,那答案一定是这样。如果这区间并没有被区间 i 包含,那这条区间与区间 i 的交集一定不大于任何被区间 i 包含的区间与区间 i 的交集。(因为这条线段的长度本身就最小了),这种情况说明区间 i 最优匹配的区间 j 一定不包含在区间 i 内部,所以我们不需要真的判断长度最小的区间是否被区间 i 包含,只要假设它被区间 i 包含即可,即使不包含,对答案也没有影响(因为答案是在 情况一二四中出现)。

对于第四种情况,在情况一二中已经算出来了。

具体代码如下:

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define pi acos(-1)
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define mkp make_pair
#define all(x) x.begin(), x.end()
#define ul (u << 1)
#define ur (u << 1 | 1)

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;

const int N = 500010, M = 110, INF = 1e9;
const int mod = 1e9 + 7;
int T = 1;
int res = 0;
int n, m, k;

struct Line
{
    int l, r;
    int len;
}s[N];

void solve()
{
    cin >> n >> m;
    int minr = INF, maxl = 0;
    int minlen = INF;
    for (int i = 1; i <= n; i ++ )
    {
        int l, r;
        cin >> l >> r;
        int len = r - l + 1;
        minr = min(minr, r), maxl = max(maxl, l);
        minlen = min(minlen, len);
        s[i] = {l, r, len};
    }
    
    res = 0;
    for (int i = 1; i <= n; i ++ )
    {
        res = max(res, min(s[i].len, s[i].r - minr));
        res = max(res, min(s[i].len, maxl - s[i].l));
        res = max(res, min(s[i].len, s[i].len - minlen));
    }
    cout << res * 2 << endl;
}

signed main()
{
    quick_cin();
    cin >> T;
    while (T -- )
    {
        solve();
    }
}

E. MEX of LCM

题意:给定一个长度为 n 的序列a,求它的所有连续子序列不可能构造出的最小的最小公倍数 x

题解:

考虑已知一个区间的 lcm ,此时将区间往两边扩大 1 ,lcm 是不降的。

于是如果我们想要知道所有区间的 lcm 中最小的未出现过数,因此我们不需要一开始就知道所有的 lcm ,考虑按从小到大构造出 lcm ,又知道区间 lcm 在区间变大的过程中是一直增大的。

想到使用小根堆,最开始将每个位置上的数存进去表示以这个位置作为区间 lcm 的起点,用 re s 表示当前考虑 re s 是否是答案,每次取出最小的数出来看是否等于 re s ,如果等于 re s 那么 re s 就不能作为答案, re s 需要加 1 ,然后将堆顶的元素取出来让它的区间往右走一步再重新插进堆中,这样就能当堆顶元素不等于 re s 时就找到了答案。

但此时若遇到 1,1,1,1,1,1,1... 这种特殊情况时间复杂度会到 O(N^2) ,于是考虑优化。

考虑什么样的情况是不需要重复计算的,当有若干个区间右端点相同,左端点不同的区间的 lcm 相同时,只需要保留一个区间即可,例如: \left [ 1,4 \right ] 的 lcm 等于 \left [ 3,4 \right ] 的 lcm 时, \left [ 1,4 \right ] 便不需要继续往右走了,因为答案会完全和 \left [ 3,4 \right ] 相同。

因此考虑记忆每个位置曾经出现过哪些值,当某个区间的右端点扩大后发现这个位置曾经有过区间的 lcm 和自己相同,那么就不用将这个区间加入堆中了,用 set 记忆这些值即可。

具体代码如下:

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define pi acos(-1)
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define mkp make_pair
#define all(x) x.begin(), x.end()
#define ul (u << 1)
#define ur (u << 1 | 1)

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;

const int N = 500010, M = 110, INF = 1e9;
const int mod = 1e9 + 7;
int T = 1;
int res = 0;
int n, m, k;
int a[N];

int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

int lcm(int a, int b)
{
    return a * b / gcd(a, b);
}

set<int> S[N];
priority_queue<PII, vector<PII>, greater<PII>> heap;

void solve()
{
    res = 1;
    cin >> n;
    for (int i = 1; i <= n; i ++ )
    {
        cin >> a[i];
        S[i].clear();
        S[i].insert(a[i]);
        heap.push({a[i], i});
    }
    
    while (heap.size() && heap.top().first == res)
    {
        while (heap.size() && heap.top().first == res)
        {
            PII t = heap.top();
            heap.pop();
            int x = t.first, idx = t.second;
            if ( ++ idx <= n)
            {
                x = lcm(x, a[idx]);
                if (!S[idx].count(x))
                {
                    heap.push({x, idx});
                    S[idx].insert(x);
                }
            }
        }
        res ++ ;
    }
    
    while (!heap.empty()) heap.pop();
    cout << res << endl;
}

signed main()
{
    quick_cin();
    cin >> T;
    while (T -- )
    {
        solve();
    }
}

F. Typewriter

题意:有一个只有一个缓冲区的打字机,即每次最多只能搬运一个数字。

打字机有以下五种操作:

  1. 如果当前单元格中的整数不为空,则从该单元格中提取该整数,如果为空,请将其放入回车缓冲区(该缓冲区最多可包含一个整数)。
  2. 如果整数不为空,则将其从回车缓冲区放入当前单元格(如果为空)。
  3. 如果缓冲区和单元格都包含整数,则将回车缓冲区中的数字与当前单元格中的数字交换。
  4. 将托架从当前单元格中移出 i 到单元格 i+1(如果 i<n ), 而缓冲器中的整数被保留。
  5. 重置支架,即将其移动到单元格编号 1 ,同时保留缓冲区中的整数。

现有三种修改:

  • 当 op=1,表示将序列向左移动 k 位。
  • 当 op=2,表示将序列向右移动 k 位。
  • 当 op=3,表示将序列整体翻转。

求让序列变成升序序列,操作5需要的最小操作次数。

题解:

不难发现操作数量等于 i>p_i 的点的数量。

我们发现无论如何修改。可能的结果只有 2n 种,我们可以把这些结果都预处理出来,然后维护操作后到达了哪个状态, O(1) 查询即可。

具体代码如下:

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define pi acos(-1)
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define mkp make_pair
#define all(x) x.begin(), x.end()
#define ul (u << 1)
#define ur (u << 1 | 1)

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;

const int N = 500010, M = 110, INF = 1e9;
const int mod = 1e9 + 7;
int T = 1;
int res = 0;
int n, m, k;
int a[N];
int ans[2][N];
vector<int> p;

void cal(int type)
{
    vector<int> c(n);//表示向右走i步后总体贡献会加c[i-1]
    int sum = 0;
    for (int i = 0; i < n; i ++ )
    {
        sum += (i > p[i]);
        c[(p[i] - i + n) % n] += 1;
    }
    ans[type][0] = sum;
    for (int i = 1; i < n; i ++ )
    {
        sum += c[i - 1] - 1;
        ans[type][i] = sum;
    }
}

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
    {
        int x;
        cin >> x;
        p.pb(x - 1);
    }
    
    cal(0);
    reverse(all(p));
    cal(1);
    
    int cnt = 0, type = 0;
    cout << ans[type][cnt] << endl;
    cin >> m;
    while (m -- )
    {
        int op;
        cin >> op;
        if (op == 1)
        {
            int x;
            cin >> x;
            cnt = (cnt - x + n) % n;
        }
        else if (op == 2)
        {
            int x;
            cin >> x;
            cnt = (cnt + x) % n;
        }
        else
        {
            type ^= 1;
            cnt = (n - cnt) % n;
        }
        cout << ans[type][cnt] << endl;
    }
    
    
}

signed main()
{
    quick_cin();
//    cin >> T;
    while (T -- )
    {
        solve();
    }
}

Codeforces Round 875 (Div. 2)

D. The BOSS Can Count Pairs

题意:给定两个序列 a,b ,问满足 a_i + a_j = b_i \times b_j 的数量,其中 1\leqslant i < j\leq n 。

题解:遍历所有的 a_i 的可能值,遍历所有的 \left \{ a_i,b_j \right \},得到理想值 b_i ,加上产生的个数。

具体代码如下:

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define pi acos(-1)
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define mkp make_pair
#define all(x) x.begin(), x.end()
#define ul (u << 1)
#define ur (u << 1 | 1)

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;

const int N = 200010, M = 110, INF = 1e18;
const int mod = 1e9 + 7;
int T = 1;
int res = 0;
int ans = 0;
int n, m, k;
int idx;
int a[N], b[N];
PII c[N];

void solve()
{
    res = 0;
    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> a[i];
    for (int i = 0; i < n; i ++ ) cin >> b[i];
    for (int i = 0; i < n; i ++ ) c[i] = {a[i], b[i]};
    sort(c, c + n);
    
    for (int val = 1; val * val <= 2 * n; val ++ )
    {
        vector<int> cnt(n + 1, 0);
        
        for (int i = 0; i < n; i ++ )
        {
            int A = c[i].first, B = c[i].second;
            if (A < val) continue;
            
            int t = val * A - B;
            
            if (t >= 1 && t <= n) res += cnt[t];
            if (A == val) cnt[B] ++ ;
        }
    }
    
    cout << res << endl;
}

signed main()
{
    quick_cin();
    cin >> T;
    while (T -- )
    {
        solve();
    }
}

E. Hyperregular Bracket Strings

题意:给出 n 和 k ,有 k 组区间 l,r ,要求满足每个区间都是匹配的括号序列,且自己整体也是一个匹配的括号序列,长度为 n ,求满足条件的括号序列的数量,答案对 998244353 取模。

题解:匹配的括号序列的数量即卡特兰数,即对于 n \times 2 个括号合法数量有 f_n = \frac{1}{n+1}C_{2n}^{n} 个。

然后我们来分析两种不同区间类型:

1、对于区间 \left [ l_1,r_1 \right ],\left [ l_2,r_2 \right ] ,其中 l_1 \leqslant l_2 \leqslant r_2 \leqslant r_1

不难发现,我们可以把它分成两个整体:\left [ l_2,r_2 \right ] 和 \left [ l_1, l_2 - 1 \right ] \bigcup \left [ r_2+1,r_1 \right ] 。

2、对于区间 \left [ l_1,r_1 \right ],\left [ l_2,r_2 \right ] ,其中 l_1 < l_2 \leqslant r_1 \leqslant r_2

不难发现,我们可以把它分成三个整体:\left [ l_1,l_2-1 \right ] 、 \left [ l_2,r_1 \right ] 和 \left [ r_1+1,r_2 \right ] 。

每个整体之间互相独立,于是我们可以通过用异或一个随机数来标记区间,然后利用异或前缀和来记录每种整体的总长度,并算出它的贡献。

注意的是本题需要的随机数范围较大,不能使用 rand() 函数,需使用 mt19937 随机数,使用函数定义如下:

mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());

具体代码如下:

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <random>
#include <chrono>
#define int long long
#define pi acos(-1)
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define mkp make_pair
#define all(x) x.begin(), x.end()
#define ul (u << 1)
#define ur (u << 1 | 1)

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;

const int N = 500010, M = 110, INF = 1e18;
const int mod = 998244353;
int T = 1;
int res = 0;
int ans = 0;
int n, m, k;
int idx;
int a[N];
int inv[N];
int fact[N], infact[N];
int f[N];

mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());

int qmi(int a, int b, int p)
{
    int res = 1;
    while (b)
    {
        if (b & 1) res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}

void init(int n)
{
    fact[0] = infact[0] = inv[0] = inv[1] = 1;
    for (int i = 2; i <= n; i ++ )
        inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
    for (int i = 1; i <= n; i ++ )
    {
        fact[i] = 1ll * fact[i - 1] * i % mod;
        infact[i] = 1ll * infact[i - 1] * inv[i] % mod;
    }
}

int C(int n, int m)
{
    if (n < m) return 0;
    if (m == 0 || n == m) return 1;
    return 1ll * fact[n] * infact[m] % mod * infact[n - m] % mod;
}

void add(int l, int r)
{
    int x = rnd();
    a[l] ^= x, a[r + 1] ^= x;
}

void solve()
{
    res = 1;
    map<int, int> cnt;
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) a[i] = 0;
    
    while (m -- )
    {
        int l, r;
        cin >> l >> r;
        add(l, r);
    }
    
    for (int i = 1; i <= n; i ++ )
    {
        a[i] ^= a[i - 1];
        cnt[a[i]] ++ ;
    }
    
    f[0] = 1;
    for (int i = 1; i <= n; i ++ )
        if (i % 2 == 0)
            f[i] = C(i, i / 2) * qmi(i / 2 + 1, mod - 2, mod) % mod;
    
    for (auto i : cnt)
        res = res * f[i.second] % mod;

    cout << res << endl;
}

signed main()
{
    init(N - 2);
    quick_cin();
    cin >> T;
    while (T -- )
    {
        solve();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值