2022牛客多校07题解 CFGJK

牛客多校07题解 CFGJK

赛时CFG(都是1A,开心) + 补题JK
https://ac.nowcoder.com/acm/contest/33192

C - Constructive Problems Never Die

题意

给定数列a,求构造同等长度的数列p,满足所有 \(p_i\neq a_i\)

分析

先按照 \(1,2,...,n\) 升序构造p,顺便把\(p_i=a_i\)的所有位置 \(i\) 记录下来
若冲突数为 \(cnt\),则:

  1. \(cnt=0\), 无冲突,直接输出
  2. \(cnt=1\), 有一个冲突,要在其他p位置上能找到一个能交换的 \(p_i\)
  3. \(cnt>1\), 有多个冲突,由于构造的时候保证了所有 \(p_i\) 互不相同,所以只需将这些冲突的交换下顺序即可(偏移一位)

Code

#include <bits/stdc++.h>

using namespace std;
const int N = 100005;
int a[N], ans[N], n;

void solve () {
    scanf ("%d", &n);
    vector <int> v;
    for (int i = 1; i <= n; i ++) {
        scanf ("%d", &a[i]);
        ans[i] = i;
        if (i == a[i])  v.push_back(i);
    }

    int m = v.size();
    if (m == 1) {
        bool all_same = true;
        for (int i = 1; i <= n; i ++) {
            if (a[i] != a[v[0]]) {
                swap (ans[i], ans[v[0]]);
                all_same = false;
                break;
            }
        }
        if (all_same) {
            printf ("NO\n");
            return ;
        }
    }

    else if (m > 1) {
        //冲突的集体偏移一位即可
        int tmp = ans[v[m-1]];
        for (int i = m-1; i > 0; i --)
            ans[v[i]] = ans[v[i-1]];
        ans[v[0]] = tmp;        
    } 
    
    printf ("YES\n");
    for (int i = 1; i <= n; i ++)
        printf ("%d ", ans[i]);
    printf("\n"); 
}

int main () {
    int t;
    scanf ("%d", &t);
    while (t --) {
        solve ();
    }
}
//按照升序构造,找冲突
//冲突数>1,偏移构造
//冲突数=1,枚举可能交换的情况
//冲突数=0,直接输出

//要用scanf

F - Candies

题意

数列 \(a\) 种相邻两个一样的数字或相邻两个和为x的数字可以被一起删掉
问最多能删多少次
注:\(a_1\)\(a_n\) 也相邻

分析

对于每一对可删的向外拓展比如当前删了\(a_i,a_j\),则接下来看\(a_{i-1},a_{j+1}\),若可删,则继续看\(a_{i-2},a_{j+2}\)...以此类推,若拓展到某一对没法删除,则中断拓展(对应到代码中就是出队),继续找下一对能删的

用deque可以实现便捷操作

Code

#include <bits/stdc++.h>

using namespace std;
const int N = 1e5 + 5;
int n, x, a[N], ans;

int main () {
    cin >> n >> x;
    for (int i = 1; i <= n; i ++)   cin >> a[i];
    deque <int> q;
    for (int i = 1; i <= n; i ++) {
        if (q.empty())  q.push_back (a[i]);
        else {
            int cur = q.back();
            if (cur == a[i] || cur + a[i] == x)     q.pop_back (), ans ++;
            else    q.push_back(a[i]);
        }
    }

    //仍有剩余则队内合并
    while (q.size() > 1) {
        if (q.front() == q.back() || q.front() + q.back() == x) {
            ans ++, q.pop_back(), q.pop_front();
        }
        else    break; //不能连续向外拓展了
    }
    cout << ans << endl;
}


//1. 相邻两个一样的数字删掉
//2. 相邻两个和为x的数字删掉
//问最多能删多少次
//注:a[1]与a[n]也相邻

//对于一对可以拓展的,往两边延伸

G - Regular Expression

题意

给了一堆符号,分别有不同的含义(正则表达式)

现给n个字符串,分别求出每个字符串使用上述符号能表示出来的最小长度,以及最小程度下有多少种可能的表示。(最小长度+种类

分析

虽然给了很多符号,但是实际上能实现减少长度的符号表示就只有* + .这三种
*代表复制*前的字符0至无穷次
+代表复制+前的字符1至无穷次
.可以指带任一字符

按照字符串长度可划分为三类:

  1. 长度为1,只可能是a, .共2种可能(自身/.指代)
  2. 长度为2,若所有字符相同,则有aa, a., .a, a*, a+, .., .*, .+共8种可能;若有字符不同,则有aa, a., .a, .., .*, .+共6种可能
  3. 长度大于2,若所有字符相同,则有a*, a+, .*, .+共4种可能;若有字符不同,则有.*, .+共2种可能

Code

#include <bits/stdc++.h>

using namespace std;

bool same (string s) {
    for (int i = 1; i < s.size(); i ++)
        if (s[i] != s[i-1]) 
            return false;
    return true;
}

void solve () {
    string s;
    cin >> s;
    int n = s.size(), len = 2, cnt;
    if (n == 1)     len = 1, cnt = 2; //a, .
    else if (n > 2) {
        if (same (s))   cnt = 4; //a*, a+, .*, .+
        else    cnt = 2; //.*, .+
    }
    else if (n == 2) {
        if (same(s))    cnt = 8; //aa, a., .a, a*, a+, .., .*, .+
        else    cnt = 6; //ab, a., .b, .*, .+, ..,
    }
    cout << len << ' ' << cnt << endl;
}

int main () {
    int t;
    cin >>  t;
    while (t --) {
        solve ();
    }
}


//有用的就只有* + .
//长度分为 1,2,>2考虑

J - Melborp Elcissalc

题意

给定一个数字 k, 问有多少种长度为 n的数组,满足:

  1. 数的范围为 [0,k−1]
  2. 有 t 个区间,其区间和为 k 的整数倍。

分析

转化为前缀和数组,区间和为k的倍数就等价于\((pre[r]-pre[l-1])\%k==0\)
\(pre[r]\%k==pre[l-1]\%k\),即前缀和数组有两点 \(\%k\) 后相等则存在这样的区间

\([0,k-1]\)在pre[]中出现了\(cnt\)次,则会有\(\C_{cnt}^2\)个区间

\(f[i][j][r]\): 拿\([0,i]\)范围内的数,已经拿了 \(j\) 个数,数组\(goodness\)\(r\) 的方案数
\(cnt[i]:\) 前缀和数组 \(s\) 下,模数\(i\) 出现的次数

转移:
\(f[i][j][r] += f[i-1][j-cnt][r-C[cnt][2]] * C[j][cnt];\) 选两个相同的端点
初始化:长度为0时,\([1,i]\)\(goodness\)\(\C_2^i\)

记得预处理出组合数

Code

#include <bits/stdc++.h>
#define int long long

using namespace std;
const int N = 70, mod = 998244353;
int C[N][N], f[N][N][N*N]; //拿[0,i]范围内的数,已经拿了j个数,数组goodness为r的方案数

void pre () {
    for (int i = 0; i < N; i ++) 
        for (int j = 0; j <= i; j++) {
            if (j == 0)     C[i][j] = 1;
            else    C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod;
        }
    
    for (int i = 0; i <= 65; i ++) {
        f[0][i][(i+1)*i/2] = 1; //长度为0时有一种方案
    }
}

signed main () {
    pre();
    int n, k, t;
    cin >> n >> k >> t;
    for (int i = 1; i < k; i++)
        for (int j = 0; j <= n; j++)
            for (int cnt = 0; cnt <= j; cnt++)
                for (int r = C[cnt][2]; r <= t; r++)
                    f[i][j][r] = (f[i][j][r] + f[i-1][j-cnt][r-C[cnt][2]]*C[j][cnt]) % mod;

    k --;
    cout  << f[k][n][t] << endl;
}

//f[i][j][r]: 拿[0,i]范围内的数,已经拿了j个数,数组goodness为r的方案数
//cnt[i]:前缀和数组s下,模数i出现的次数
//i出现的次数为cnt
//f[i][j][r] += f[i-1][j-cnt][r-C[cnt][2]] * C[j][cnt]; //选两个相同的端点
//长度为0时,[1,i]的goodness为C[2][i]

//预处理出组合数

K - Great Party

博弈论+莫队

题意

两个人玩石子游戏。有多堆石子,两个人轮流操作。

每一轮,都必须选择一堆石子,拿走其中的至少一个石子。

然后你可以将选择的这一堆石子,和另外的一堆石子合并,也可以不合并。

现有m个询问,每个询问下都有区间[L,R],问该区间下有多少个子区间,在该子区间上先手必胜

分析

区间长度为奇数时先手必胜,因为
先手可以通过一次操作,带上合并,使得出现两个相同的数。转化为必胜态。

区间长度为偶数时转化为\(a_i-1\)的Nim游戏,因为
两人都不会在这一轮减少石子堆数(即进行合并操作或拿完一堆石子),原因是这么做会使得下一个人进入必胜态。那么该僵局会在1,1,...,1时被打破(此时下一个操作的人必定会使得石子堆数减少,即必输)。类比推理Nim游戏最后会出现0,0,...,0
的局面,容易将本问题转化为\(a_i-1\)的nim游戏

分奇偶,用莫队统计即可

Code

#include <bits/stdc++.h>
#define int long long

using namespace std;
const int N = 1e5 + 5, M = 4000000;
int n, m, len, num;
int a[N], pre[N], ans[N], cot[2][M]; //分奇偶统计

struct Node {
    int l, r, id;
    bool operator<(const Node &t) const {
        if (l/len != t.l/len)   return l < t.l;
        if (l/len & 1)  return r < t.r;
        return r > t.r; //奇偶排序优化
    }
}Q[N];

inline int c2(int n) {
    return n * (n - 1) / 2;
} //组合数

inline void add(int p){
    // pre[p] 
    num -= c2(cot[p % 2][pre[p]]);
    cot[p % 2][pre[p]]++;
    num += c2(cot[p % 2][pre[p]]);
}

inline void del(int p){
    num -= c2(cot[p % 2][pre[p]]);
    cot[p % 2][pre[p]]--;
    num += c2(cot[p % 2][pre[p]]);
}

signed main () {
    cin >> n >> m;
    len = sqrt(n);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        pre[i] = pre[i-1] ^ (a[i] - 1); //a[i]-1的nim游戏
    }    
    for (int i = 0; i < m; i++) {
        int l, r;
        cin >> l >> r;
        l --; //[l-1, r]
        Q[i] = {l, r, i};
    }
    sort(Q, Q + m);

    //后面直接套莫队
    int l = 1, r = 0;
    for (int i = 0; i < m; i ++) {
        int L = Q[i].l, R = Q[i].r;
        while (l > L)   add (--l); //左端点左移+
        while (r < R)   add (++r); //右端点右移+
        while (l < L)   del (l++); //左端点右移-
        while (r > R)   del (r--); //右端点左移-
        ans[Q[i].id] = c2(Q[i].r - Q[i].l + 1) - num;
    }
    for (int i = 0; i < m; i++)    cout << ans[i] << endl;
}


//区间长度为奇数时,先手必胜。
//区间长度为偶数时,执行减一的Nim游戏。
posted @ 2022-08-12 16:51  Sakana~  阅读(53)  评论(0编辑  收藏  举报