Codeforces Round 1061 (Div. 2) 记录

T1

很显然,其实每次让浩拿一个,每次让亚历克斯拿一个,剩下的留到第二天绝对不会比其他解差,所以,分类讨论,当 \(n\) 是偶数的时候:

\[(1,1,n-2),(1,1,n-4),(1,1,n-6),...,(1,1,2) \]

这个三元序列的长度,就是 \(n\) 是偶数时浩能拿到的最大数量。那么显然答案是 \(\frac{n}{2}-1\)。

当 \(n\) 是奇数的时候:

\[(1,1,n-2),(1,1,n-4),(1,1,n-6),...(1,1,1) \]

这个三元序列的长度,就是 \(n\) 是奇数时浩能拿到的最大数量。显然答案是 \(\lfloor\frac{n}{2}\rfloor\)。

这里已经可以做了,但是可以更简单。

发现不管 \(n\) 是偶数还是奇数,答案都可以通用:

\[\left\lfloor\frac{n-1}{2}\right\rfloor \]

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int _;
    cin >> _;
    while(_--)
    {
        int n;
        cin >> n;
        cout << (n-1>>1) << '\n';
    }
    return 0;
}

T2

首先看到 \(n \le 20\),虽然 \(a_i \le 10^9\),但是你会发现当 \(1 \sim n\) 中只要有一台 B 机器,那么这一次轮回至少除以 \(2\),所以复杂度最多是 \(O(qn \log V)\),\(V\) 是值域,但你会发现如果 \(1 \sim n\) 中全都是 A 机器,那么这么做,复杂度就是 \(O(qV)\) 的,会 T 得飞起,但是你会发现如果 \(1 \sim n\) 中全都是 A 机器的话,那对于任意 \(a_i\) 答案显然是 \(a_i\),所以特判即可通过。

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
char s[25];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int _;
    cin >> _;
    while(_--)
    {
        int n,q;
        cin >> n >> q;
        cin >> s+1;
        int A = 0,B = 0;
        for(int i = 1;i<=n;++i)
        {
            A+=s[i] == 'A';
            B+=s[i] == 'B';
        }
        while(q--)
        {
            int x;
            cin >> x;
            if(B == 0)
            {
                cout << x << '\n';
            }
            else
            {
                int num = 0;
                while(x)
                {
                    for(int i = 1;i<=n;++i)
                    {
                        if(!x)
                        {
                            break;
                        }
                        ++num;
                        if(s[i] == 'A')
                        {
                            --x;
                        }
                        else
                        {
                            x>>=1;
                        }
                    }
                }
                cout << num << '\n';
            }
        }
    }
    return 0;
}

T3

这题就很数学了,很练思维。

首先当一个数是 \(g\) 的倍数时,它不需要做任何操作,这是显然的。

然后给出一些结论:

  1. 我们完全可以 删除数分裂数,答案不会更劣。
  2. 设我们要使所有数都是 \(g\) 的倍数,对于一个数 \(x\) 它如果 \\ge 4g,那它一定 可以通过分裂来使得分成的两个数都是 \(g\) 的倍数。
  3. 如果 \(x < 4g\),并且 \(x\) 不是 \(g\) 的倍数,那么 \(x\) 一定没有办法 通过分裂来使得分成的两个数都是 \(g\) 的倍数,所以只能删除。

然后来证明:

  1. 采用反证法,假设有一个最优解,对于某个数 \(x\),它将 \(x\) 分成了 \(x_1,x_2,x_3\),然后删除了 \(x_1\) 或 \(x_3\),那么我们完全可以先删掉 \(x\),然后就不会有分裂操作,这样的话这个序列相当于少了一个数(\(x_1\) 或 \(x_2\)),对于这道题,少了一个数只可能更优或相等,但不会更劣。
  2. 我们完全可以将 \(x\) 拆成 \((g,g+x \mod g,x-2g-x \mod g)\),然后很显然满足 \(g \le g+x \mod g \le x-2g-x \mod g\),并且 \(g\) 和 \(x-2g-x \mod g\) 显然都是 \(g\) 的倍数。
  3. 使用数学归纳法,设 \(P_x\) 表示这个命题对于 \(x\) 的真假,那么 \(P_1\) 肯定成立,因为 \(1\) 无法分割,那么考虑 \(P_x\),并且 \(\forall_{i(1 \le i \le x-1)} P_{i} = 1\),当然 \(2 \le x < 4g,x \mod g \not = 0\),因为如果 \(x \ge 4g\) 或者 \(x \mod g = 0\),那 \(P_x\) 自然成立(前面已经证明),没意义。假设我们将 \(x\) 分成 \(x_1,x_2,x_3\),并且 \(x_1+x_2+x_3 = x\),那根据前面说的,\(P_{x_1}\) 和 \(P_{x_3}\) 都是无法分割使得分成的两个数都是 \(g\) 的倍数,所以想要证反,只有可能 \(x_1\) 和 \(x_3\) 都是 \(g\) 的倍数,如果 \(x_1 \ge 2g\),则 \(x_3 \ge x_2 \ge x_1\),那么 \(x = x_1+x_2+x_3 \ge 2g+2g+2g \ge 6g\),显然和 \(x<4g\) 矛盾,因此,唯一的可能是 \(x_1 = g\)。假设 \(x_3 = g\),那么根据 \(x_1 \le x_2 \le x_3\) 得出 \(x_1 = x_2 = x_3 = g\),那么 \(x = 3g\),与 \(x\) 不是 \(g\) 的倍数矛盾。最后就是 \(x_3>2g\),于是 \(x = x_1+x_2+x_3 \ge g+g+2g = 4g\),与 \(x<4g\) 矛盾。

证明完后, 你会发现,对于一个 \(g\),它可行当且仅当:

\[[x \ge g]+[x = g]+[x = 2g]+[x = 3g] \ge n-k \]

然后你会发现可以用一个桶来记录每个数出现次数,那么就解决了第 \(2 \sim 4\) 个式子,然后第 \(1\) 个也就是加一个前缀和就行了。

所以说遍历 \(g = [1,n]\),然后逐个判断是否可行即可。

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int a[N];
int pre[N];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int _;
    cin >> _;
    while(_--)
    {
        int n,k;
        cin >> n >> k;
        for(int i = 1;i<=n;++i)
        {
            a[i] = 0;
        }
        for(int i = 1;i<=n;++i)
        {
            int x;
            cin >> x;
            ++a[x];
        }
        for(int i = 1;i<=n;++i)
        {
            pre[i] = pre[i-1]+a[i];
        }
        int ans;
        for(int i = 1;i<=n;++i)
        {
            if(n-pre[min(n,(i<<2)-1)]+a[i]+((i<<1)>n?0:a[i<<1])+(i*3>n?0:a[i*3])>=n-k)
            {
                ans = i;
            }
        }
        cout << ans << '\n';
    }
    return 0;
}

T4

喜提 \(8\) 个 WA!

我们很显然有一个 \(n (\lfloor \log n \rfloor+1)\) 次查询的做法,设 \(f_k(x)\) 表示 \(x\) 的第 \(k\) 位的值,显然我们只需要求 \(p_n\) 的所有第 \(k(0 \le k \le \lfloor \log n \rfloor)\) 就能知道 \(p_n\) 的值,求出 \(p_n\) 第 \(k\) 位的方法很简单,根据简单原理得出:

\[f_k(p_n)=\sum_{i = 1}^n f_k(i)-\sum_{i = 1}^{n-1} f_k(p_i) \]

然后仔细观察,发现可以用一种神奇的方法给等式右边同时减去那些 \(1 \sim i-1\) 位有和 \(p_n\) 不相同的的数,你就会得到这个:

\[f_k(p_n) = \sum_{i \in S_{k-1}} f_k(i)-\sum_{i \in P_{k-1}} f_k(p_i) \]

这里 \(S_x\) 指的是 \(1 \sim x-1\) 都和 \(p_n\) 相同的数所构成的集合,\(P_x\) 指的是 \(1 \sim x-1\) 都和 \(p_n\) 相同的 \(p_i(1 \le i \le n-1)\) 的下标 \(i\) 所构成的集合。

然后你就会发现,这个东西的询问次数其实是:

\[\sum_{k = 0}^{\lfloor \log_2 n \rfloor} \left\lceil \frac{n-1}{2^k} \right\rceil \]

\[= (n-1)+\sum_{k = 1}^{\lfloor \log_2 n \rfloor} \left\lceil \frac{n-1}{2^k} \right\rceil \]

\[\le (n-1)+\sum_{k = 1}^{\lfloor \log_2 n \rfloor} \frac{n-1}{2^k}+1 \]

\[\le (n-1)+(n-1)\sum_{k = 1}^{\lfloor \log_2 n \rfloor} \frac{1}{2^k}+\sum_{k = 1}^{\lfloor \log_2 n \rfloor}1 \]

\[\le (n-1)+(n-1)(1-\frac{1}{2^{\lfloor \log_2 n \rfloor}})+\lfloor \log_2 n \rfloor \]

\[\le (n-1)+(n-1)(1-\frac{1}{2^{\lfloor \log_2 n \rfloor}})+\lfloor \log_2 n \rfloor \]

\[\le (n-1)+(n-1)(1-\frac{1}{n})+\lfloor \log_2 n \rfloor(\operatorname{beacause} 2^{\lfloor \log_2 n \rfloor} \le n) \]

\[\le (n-1)+(n-1)-\frac{n-1}{n}+\lfloor \log_2 n \rfloor \]

\[\le 2n-2-\frac{n-1}{n}+\lfloor \log_2 n \rfloor \]

\[\le 2n-2-(1-\frac{1}{n})+\lfloor \log_2 n \rfloor \]

\[\le 2n-3+\frac{1}{n}+\lfloor \log_2 n \rfloor \]

虽然这玩意看似在 \(n\) 比较极限的时候会超出 \(2n\) 一点点,但这只是很粗略的上界,实际明显低于这个。

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e4+5;
const int mx = 2e4;
int a[N];
int b[N];
int bx[N];
int by[N];
int P[N];
int hou[N];
int dian[N];
int NP[N][2];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    for(int i = 1;i<=mx;++i)
    {
        int s = (i&1);
        b[i] = b[i-1]+s;
        bx[i] = bx[i-1];
        by[i] = by[i-1];
        if(s)
        {
            bx[i]+=((i&2)>0);
        }
        else
        {   
            by[i]+=((i&2)>0);
        }
    }
    int _;
    cin >> _;
    while(_--)
    {
        int n;
        cin >> n;
        int cnt = 0,numx = 0;
        for(int i = 1;i<n;++i)
        {
            cout << "? " << i << " 1" << endl;
            cin >> dian[i];
            cnt+=dian[i];
        }
        int w = b[n]-cnt;
        hou[0] = w;
        for(int i = 1;i<n;++i)
        {
            if(dian[i] == w)
            {
                P[++numx] = i;
            }
        }
        int ans = w;
        int s = 31-__builtin_clz(n);
        int num1 = 0,num2 = (w == 0?by[n]:bx[n]);
        int nx = 0,ny = 0;
        for(int i = 1;i<=numx;++i)
        {
            cout << "? " << P[i] << " 2" << endl;
            int x;
            cin >> x;
            num1+=(x>0);
            if(!x)
            {
                NP[++nx][0] = P[i];
            }
            else
            {
                NP[++ny][1] = P[i];
            }
        }
        for(int i = 1;i<=s;++i)
        {
            ans+=(1<<i)*(num2-num1);
            if(i == s)
            {
                continue;
            }
            w = num2-num1;
            hou[i] = w;
            for(int j = 1;j<=(w?ny:nx);++j)
            {
                P[j] = NP[j][w];
            }
            numx = (w?ny:nx);
            nx = 0,ny = 0;
			num1 = 0;
            for(int j = 1;j<=numx;++j)
            {
                cout << "? " << P[j] << ' ' << (1<<i+1) << endl;
                int x;
                cin >> x;
                num1+=(x>0);
                if(!x)
                {
                    NP[++nx][0] = P[j];
                }
                else
                {
                    NP[++ny][1] = P[j];
                }
            }
            num2 = 0;
            for(int j = 1;j<=n;++j)
            {
                int dui = 1;
                for(int k = 0;k<=i;++k)
                {
                    int flag = ((j&(1<<k))>0);
                    if(flag!=hou[k])
                    {
                        dui = 0;
                        break;
                    }
                }
                if(dui)
                {
                    num2+=((j&(1<<i+1))>0);
                }
            }
        }
        cout << "! " << ans << endl;
    }
    return 0;
}