CF Round 769 Div2 题解

A题 ABC (构造)

给定一个长度为 \(n\) 的 01 串,问这个 01 串中是否存在一个长度超过 1 的回文串?

\(n\leq 100\)

  1. \(n=1\) 是显然不存在

  2. \(n=2\) 时,如果两个字符相同,那么这个串本身就是回文的,反之则不是

  3. \(n\geq 3\) 时,我们假设第一个字符为 0,那么第二个字符则必须为 1(反之他俩会构成一个回文串),那么对于第三个字符:

    1. 若第三个字符为 0,那么前三个字符构成一个回文串(010)
    2. 若第三个字符为 1,那么第二第三个字符构成一个回文串

    第一个字符为 1 时同理,所以 \(n\geq 3\) 时必然存在回文串

#include<bits/stdc++.h>
using namespace std;
int n;
string s;
bool solve() {
    cin >> n >> s;
    if (n == 2) return s[0] != s[1];
    return n == 1;
}

int main()
{
    int T;
    cin >> T;
    while (T--) puts(solve() ? "YES" : "NO");
    return 0;
}

B题 Roof Construction (构造)

给定从 0 到 \(n-1\)\(n\) 个数,尝试找到一个排列方法,使得 \(\max\limits_{1\leq i < n}p_i\oplus p_{i+1}\) 最小,并输出方案。

\(2\leq n\leq 2*10^5\)

我们尝试找出最大的 \(x\),使得 \(2^x < n\),便发现可以使得这个结果不超过 \(2^x\):将所有大于等于 \(2^x\) 的数放在 0 的右侧(\(2^x\) 紧贴着 0 放),其余数放在左侧,我们会发现 0 右侧的数的异或值均小于 \(2^x\)(因为他们的最高位都为 1,所以相同为 0,依照异或的运算法则被消去了),而右边也一样(他们是干脆那一位都是 0,所以没得消),仅剩下 0 和 \(2^x\) 的异或值为 \(2^x\)

我们尝试再压一压,发现依据上面的任意流程,任何违反上述过程的操作都只会使得结果变大或者不变(0 和 大于 \(2^x\) 的数相连自不必说,而 0 和小于 \(2^x\) 的数相邻的话,则会使得至少一个不小于 \(2^x\) 的数和另一个小于 \(2^x\) 的数相邻,结果不会变小)

综上,我们得到构造策略:\([1,2^x),[0],[2^x,n)\)

#include<bits/stdc++.h>
using namespace std;
void solve() {
    int n;
    cin >> n;
    //这用的是lowbit的技巧
    int x = n - 1, p = 0;
    while (x) x -= (p = x & -x);
    for (int i = 1; i < p; ++i) printf("%d ", i);
    printf("0");
    for (int i = p; i < n; ++i) printf(" %d", i);
    puts("");
}
int main()
{
    int T;
    cin >> T;
    while (T--) solve();
    return 0;
}

C题 Strange Test(数学)

给定正整数 \(a,b\),保证初始状态下 \(a<b\)

现在,我们可以进行若干次不同类型的操作:让 a 自加 1、让 b 自加 1、或让 \(a=a|b\)

问至少需要多少次操作,可以使得 \(a=b\)

\(1\leq a < b \leq 10^6\)

第三个操作不会减少 a,而且会使得 \(a\geq b\),所以考虑只使用一次或者不用,并且用了之后仅进行操作 2。

如果不进行操作三,那么需要 \(b-a\) 次操作。

进行的话,假设在操作前我们使得 a 变成 x,b 变成 y,之后进行操作三之后再进行操作二,那么有总操作次数

\[cnt=(x-a)+(y-b)+1+((x|y)-y)\\ =x+(x|y)+(1-a-b) \]

后者是常量,所以我们看看前者怎么搞:

我们直接枚举 \(x\) 的值(有了 \(b-a\) 这个操作上限,所以不用担心枚举范围),然后找到最适合的,使得 \(x|y\) 值最小的 y。

我们在二进制上对 \(x\)\(b\) 从高位到低位逐位匹配,找到第一个 \(x\) 上为 1,\(b\) 上为 0 的地方,这意味着我们要让 \(b\) 加到这一位为 1,后面的位均为 0,也就是我们所得到的 \(y\)

#include<bits/stdc++.h>
using namespace std;
inline int d(int x, int k) { return (x >> k) & 1; }
int calc(int a, int b) {
    int res = 0;
    for (int k = 30; k >= 0; k--)
        if (d(b, k)) res += 1 << k;
        else if (d(a, k)) {
            res += 1 << k;
            break;
        }
    return res;
}
int solve() {
    int a, b;
    cin >> a >> b;
    int res = b - a;
    for (int x = a; x < b; ++x)
        res = min(res, x + (x | calc(x, b)) + (1 - a - b));
    return res;
}
int main()
{
    int T;
    cin >> T;
    while (T--) cout << solve() << endl;
    return 0;
}

D题 New Year Concert(数据结构,数学,二分)

对于一个长度为 \(k\) 的数列 \(\{b_n\}\),倘若存在 \(1\leq l\leq r\leq k\)\(\gcd(b_l,b_{l+1},\cdots,b_r)=r-l+1\),那么这个数列就是不完美的。

我们可以进行操作,每次选择数列的某一位,将其修改为另一个正整数,最后使得这个数列变得完美,操作次数被称为修改数列的代价。

现在给定一个长度为 \(n\) 的数组 \(\{a_n\}\),问对于这 \(n\) 个前缀数组(\([a_1],[a_1,a_2],\cdots,[a_1,a_2,\cdots,a_n]\)),他们的代价分别是多少?

\(n\leq 2*10^5,1\leq a_i\leq 10^9\)

区间 gcd,我们可以用线段树来搞定(只用区间查询,连修改都不用)

想一想,如果我们选择修改某个数,我们肯定会将其改为某个大质数(例如 \(10^9+7\)),因为这样可以使得包含这个数的区间不可能满足上面的等式(区间长度为 1 时,gcd 为 \(10^9+7\),反之为 1)。

基于此,我们想到了一个类似双指针的策略:起初让 \(L=1\),随后尽量向右移动 \(R\),当存在 \(L\leq l\leq R\)\(gcd([l,R])=R-l+1\) 时给位置 \(R\) 打上 vis 标记,然后让 \(L=R+1\),周而复始。如果 \(l\) 是暴力枚举的,那么总复杂度为 \(O(n^2\log n)\)

我们研究一下 \(gcd([l,R])=R-l+1\) 这个等式,发现一个性质:当 R 固定,l 不断左移的时候,等式右边不断增加(这个是显然的),而等式左边则不会增加、或者变小(对于 gcd 操作,加入新的数不可能使得结果变大,只会变小或者不变)。

这个性质在第一眼似乎没啥用处,不过如果我们换个说法,转换一下,那这个性质就比较显然了:若 \(gcd([x,R])=R-x+1\),那么对于任意 \(L\leq i< x\),有 \(gcd([i,R])<R-i+1\)\(x< i\leq R\) 时,则有 \(gcd([i,R])>R-i+1\)

我们在 \([L,R]\) 上二分第一个符合 \(\gcd([p,R])\leq R-p+1\) 的位置 \(p\),倘若找不到或者 \(\gcd([p,R])< R-p+1\),则说明没有 l 满足这个等式;相反,则给这个位置打上标记即可。

双指针的整体框架为 \(O(n)\),线段树和二分的复杂度均为 \(O(\log n)\),所以总复杂度为 \(O(n\log^2n)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int gcd(int a, int b) {
   if (!b) return a;
   return gcd(b, a % b);
}
int n, a[N];
namespace SegmentTree {
   struct Node {
       int l, r, val;
   } a[N << 2];
   int mp[N];
   inline int ls(int x) { return x << 1; }
   inline int rs(int x) { return x << 1 | 1; }
   inline void pushup(int x) {
       a[x].val = gcd(a[ls(x)].val, a[rs(x)].val);
   }
   void build(int x, int l, int r, int *arr) {
       a[x].l = l, a[x].r = r;
       if (l == r) {
           a[x].val = arr[l];
           return;
       }
       int mid = (l + r) >> 1;
       build(ls(x), l, mid, arr);
       build(rs(x), mid + 1, r, arr);
       pushup(x);
   }
   int query(int x, int l, int r) {
       if (l <= a[x].l && a[x].r <= r)
           return a[x].val;
       int mid = (a[x].l + a[x].r) >> 1;
       if (l <= mid && mid < r)
           return gcd(query(ls(x), l, r), query(rs(x), l, r));
       if (l <= mid) return query(ls(x), l, r);
       if (r >  mid) return query(rs(x), l, r);
       return -1;
   }
}
bool BinartSearch(int L, int R) {
   int l = L - 1, r = R;
   while (l < r) {
       int mid = (l + r + 1) >> 1;
       if (SegmentTree::query(1, mid, R) <= R - mid + 1) l = mid;
       else r = mid - 1;
   }
   return (l == L - 1 || SegmentTree::query(1, l, R) != R - l + 1);
}
int vis[N];
int main()
{
   //read
   scanf("%d", &n);
   for (int i = 1; i <= n; ++i)
       scanf("%d", &a[i]);
   //solve
   SegmentTree::build(1, 1, n, a);
   int L = 1, R;
   while (L <= n) {
       for (R = L; R <= n; ++R)
           if (!BinartSearch(L, R)) {
               vis[R] = 1;
               break;
           }
       L = R + 1;
   }
   int res = 0;
   for (int i = 1; i <= n; ++i)
       printf("%d ", res += vis[i]);
   return 0;
}
posted @ 2022-03-02 20:15  cyhforlight  阅读(31)  评论(0编辑  收藏  举报