Codeforces补题4.0

不补题目一时爽,一直不补一直爽(睡)

Codeforces Round 882 (Div. 2)

D. Professor Higashikata

题意:给定一个长为 n 字符串 s ,给定 m 个区间表示字符串被截取的区间,有 q 次询问,每次询问先将 s_{x_i} 异或 1 ,然后问将截取的字符串 t 字典序最大至少需要交换多少次不同位置的字符。

题解:我们贪心地来考虑,首先对于一个 s_i ,它在截取的字符串 t 中第一次出现位置是他对字典序产生贡献最大的位置,并且第一次位置出现越靠前贡献越大,由此我们对截取区间内的所有字符按照贡献大小离散排序, 我们定义一个容器 order 来存储。

想要字典序最大,于是我们就需要将 1 尽可能地往前放,而经过离散化,我们发现我们可以控制的最靠前的 1 的位数就是 min(cnt, order.size())) ,其中 cnt 就是字符串 s 中 1 的个数。

举个例子,对于样例:

6 1 0
101010
1 4

我们得到 order.size() = 4 ,字符串 s 中 1 的个数为 3 ,即 cnt=3;我们发现我们想要字典序最大即 1 都在左边,我们能够通过操作换过来的 1 的位数由容器 order 的大小和实际字符串 1 的个数决定,从上例我们发现我们只能控制最左边 3 位。

接着往下,我们发现那么每次我们最小操作数不就是  可以控制的 1 的位数 - 位数内已经有的 1 的个数(注意位数和个数的区分)

于是我们代码中分别维护一下 字符串 s 中 1 的个数 cnt 、位数内已经有的 1 的个数 cnt_ones。

具体代码如下:

#pragma GCC optimize(2)
#include <iostream>
#include <algorithm>
#include <bitset>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <map>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <vector>

#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 = 6000, M = 600, INF = 1e18;
const int mod = 998244353;
int T = 1;
int res = 0;
int ans = 0;
int n, m, k, q;
string s;

void solve()
{
    cin >> n >> m >> q;
    cin >> s;
    
    vector<int> order;//离散化
    set<int> S;
    
    for (int i = 0; i < n; i ++ ) S.insert(i);
    
    while (m -- )
    {
        int l, r;
        cin >> l >> r;
        l -- ;
        auto it = S.lower_bound(l);
        while (it != S.end() && *it < r)
        {
            order.pb(*it);
            it = next(it);
            S.erase(prev(it));
        }
    }
    
    vector<int> pos(n, -1);
    int cnt = count(all(s), '1');
    for (int i = 0; i < order.size(); i ++ ) pos[order[i]] = i;
    
    int cnt_ones = 0;//表示我们可以控制位数内已经存在的1的个数
    for (int i = 0; i < order.size() && i < cnt; i ++ )
        cnt_ones += (s[order[i]] == '1');
    
    while (q -- )
    {
        int x;
        cin >> x;
        x -- ;
        if (s[x] == '1')
        {
            cnt -- ;

            //在cnt<order.size()的情况下,取min时cnt的改变会影响可控制的位数,所以需要判断
            if (cnt < order.size())
                cnt_ones -= (s[order[cnt]] == '1');
        }
        else
        {
            if (cnt < order.size())
                cnt_ones += (s[order[cnt]] == '1');
            cnt ++ ;
        }
        
        s[x] ^= 1;
        
        if (pos[x] != -1 && pos[x] < cnt)
            cnt_ones += (s[x] == '1' ? 1 : -1);
        
        cout << min(cnt, (int)order.size()) - cnt_ones << endl;
    }
}

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

F. The Boss's Identity

题意:给定一个 n 个数的序列 a ,此后 a_i = a_{i-n} | a_{i-n+1} 其中 i>n 。问第一个下标 i 满足 a_i > x 是多少。

题解:

当 n=4 的时候,我们模拟发现:

len=1:    a1            a2            a3            a4
len=2:    a1|a2         a2|a3         a3|a4
len=3:    a4|a1|a2      a1|a2|a3      a2|a3|a4
len=4:    a3|a4|a1|a2

不难发现第 2\sim n-1 行有 n-1 个数字,第 n 行有 1 个数字。所以总数字数量是 n+(n-2)(n-1)+1 个。

每个数字都是一个子段,首先长度小的子段靠前,在长度相等的子段中,右端点小的子段靠前。

对于或/与/gcd子段有一个特点,就是序列中值不同的子段只有 nlogA 种。所以我们可以找出以每个点为右端点的所有有效的子段,即长度最小并且右端点最小的子段,这样的子段只有 nlogA 个,然后计算出每种取值至少需要多大的序号才能满足要求,查询时只需在序列中二分即可。

具体代码如下:

#pragma GCC optimize(2)
#include <iostream>
#include <algorithm>
#include <array>
#include <bitset>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <map>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <vector>
#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 = 6000, M = 600, INF = 1e18;
const int mod = 998244353;
int T = 1;
int res = 0;
int ans = 0;
int n, m, k, q;

void solve()
{
    cin >> n >> q;
    vector<int> a(n * 2);
    for (int i = 0; i < n; i ++ )
        cin >> a[i], a[i + n] = a[i];
    
    vector<array<int, 3> > cand;
    cand.reserve(n * 30);
    vector<int> last(30, -1);
    
    for (int i = 0; i < 2 * n; i ++ )
    {
        for (int j = 0; j < 30; j ++ )
            if (a[i] >> j & 1)
                last[j] = i;
        
        auto b = last;
        sort(all(b), greater<>());
        int s = 0;
        for (int j = 0; j < 30; j ++ )
        {
            if (b[j] == -1) break;
            if (!j || b[j] != b[j - 1])
            {
                s |= a[b[j]];
                int len = i - b[j] + 1;
                int ed = (i < n ? i : i - n);
                if (ed || len == 1)
                    cand.pb({s, len, ed});
            }
        }
    }
    
    sort(all(cand));
    vector<array<int, 3> > v;
    v.reserve(n * 30);
    
    PII tmp = {n + 1, 0};
    //从大到小遍历,用tmp存的位置就一直变小,与题意相应
    for (int i = cand.size() - 1; i >= 0; i -- )
    {
        int j = i;
        tmp = min(tmp, {cand[j][1], cand[j][2]});
        while (j - 1 >= 0 && cand[j][0] == cand[j - 1][0])
        {
            j -- ;
            tmp = min(tmp, {cand[j][1], cand[j][2]});
        }
        
        v.pb({cand[j][0], tmp.first, tmp.second});
        i = j;
    }
    
    reverse(all(v));
    
    auto get = [&](int x, int y)
    {
        if (x == 1) return y + 1ll;
        if (x == n) return n + 1ll * (n - 2) * (n - 1) + 1;
        return n + 1ll * (x - 2) * (n - 1) + y;
    };
    
    while (q -- )
    {
        int x;
        cin >> x;
        auto it = lower_bound(all(v), array<int, 3>{x + 1, 0, 0});
        if (it == v.end()) cout << "-1\n";
        else cout << get(it->at(1), it->at(2)) << endl;
    }
}

signed main()
{
    quick_cin();
    cin >> T;
    while (T -- )
    {
        solve();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值