2024CCPC网络赛题解

前言

开局掉线30min比较搞心态,不过比赛延迟了1个小时,但是后面也一直没过题,如果时间再少点可能排名还更好看。最后是8题前面,这里简单讲一下我有写的题,队友写的题就放一下队友的赛时代码,大家自己看看吧。

B

队友写的,签到,但是数据范围 n103 给队友整不自信了,因为答案的那个结论成立的话很显然可以 nlogn 做,可能是为了让大家能 n2 求不整齐度吧。

代码

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10;
const int M = 998244353;

int frac(int n){
    int re=1;
    for(int i=1;i<=n;i++)re=re*i%M;
    return re;
}

signed main() {
   ios::sync_with_stdio(0);
   cin.tie(0), cout.tie(0);
    int n;
    cin>>n;
    set<int>s;
    map<int,int>mp;
    int a[n];
    for(int i=0;i<n;i++)cin>>a[i],mp[a[i]]++,s.insert(a[i]);
    sort(a,a+n);
    int ans=0;
    int cnt=1;
    for(auto [p,t]:mp)cnt=cnt*frac(t)%M;
    if(s.size()>1)cnt*=2;
    for(int i=0;i<n;i++)
     for(int j=i;j<n;j++){
        ans+=a[j]-a[i];
     }
    cout<<ans<<" "<<cnt%M;

  //  system("pause");  // 交之前记得注释一下,关流的时候不会显示输出
    return 0;
}

D

队友写的,区间DP

题意

给定一个字符串 S, 然后按照题目中的规则得到一个新的串,求字符串 T 作为子序列在这个新的串里的出现次数

分析

dp[i][l][r] 表示字符串 S 考虑 [1,i] 所得到的串里,字符串 T[l,r] 这个子串对应的答案。然后对于给定的字符串 S, 每增加一位,按照规则得到的字符串相当于,假设原本已经得到了字符串 S, 在后面插入 S[i+1] 之后,再把原先得到的串 S 贴到后面,即 SS[i+1]S。所以,对于原本匹配串 T[l,r],可以枚举一下中间的分界点 k,相当于让第一个 S 出匹配串子序列的前半部分,即 dp[i][l][k],第二个 S 出后半部分dp[i][k+1][r],所以两个的方案是乘起来。
然后因为中间还有单个字符 S[i+1], 所以再把这个考虑进去,看它能跟 T[l,r] 区间的哪个位置匹配,让第一个 S出匹配之前部分子序列的方案数 dp[i][l][k1],第二个 S 出匹配之后的子序列部分的方案数 dp[i][k+1][r],二者也是相乘。

代码

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define ll long long
using namespace std;
const int N = 109;
const int mod = 998244353;
int dp[N][N][N], n, m;
string S, T;
signed main() {
     ios::sync_with_stdio(0);
     cin.tie(0), cout.tie(0);
    cin >> S >> T;
    n = S.length();
    m = T.length();
    S = ' ' + S;
    T = ' ' + T;
    for(int i = 0; i <= n; i++)
        for(int j = 1; j <= m+1; j++)
            for(int t = 0; t < j; t++)
                dp[i][j][t] = 1;
    for(int i = 1; i <= n; i++){
        for(int l = 1; l <= m; l++)
            for(int r = l; r <= m; r++){
                for(int k = l-1; k <= r; k++)
                    dp[i][l][r] = (dp[i][l][r] + dp[i-1][l][k] * dp[i-1][k+1][r]) % mod;
                for(int k = l-1; k < r; k++)
                    if(S[i] == T[k+1])
                        dp[i][l][r] = (dp[i][l][r] + dp[i-1][l][k] * dp[i-1][k+2][r]) % mod;    
            }
    }
    cout << dp[n][1][m] << '\n';
    //system("pause");  // 交之前记得注释一下,关流的时候不会显示输出
    return 0;
}

E

和队友一起推的式子,我写的代码,这里可以稍微讲讲。
对于trie树上的一个节点,表示的是一个前缀。那么这个节点存在,说明 n 个字符串里至少有一个串包括这个前缀即可,那么概率就是 1P(),而这个前缀不出现,则所有 n 个串都不能有这个前缀。显然这 n 个串是独立的,所以只需要求出一个串不包含这个前缀的概率,拿去 n 次方即可。那么对于一个串,不包含这个前缀的概率,又等于 1P(),包含这个前缀(假设长度为 i)的概率显然为 (126i)。又因为所有长度为 i 的前缀都是等价的,所以还要再最外面考虑乘一个 26i表示所有长度为 i 的前缀的数量。综上,最后trie树的期望节点数就是

1+i=1m(26i×[1(1(126)i)n])

至于最多的节点数,则分层考虑,第一层最多有 26 个,第二层最多有 262个,以此类推,第 i 层最多有 26i 个,而且因为每一层都至少对应这 n 个串中的1个,所以每一层最多的结点数还要跟 n 取个最小值,所以最后的答案就是:

1+i=1mmin(26i,n)

代码

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define ll long long
using namespace std;
const int maxn = 1e5 + 1000;
const int mod = 998244353;
int mi26[maxn];
int ksm(int a, int b) {
    int res = 1;
    for (; b; b >>= 1, a = a * a % mod) {
        if (b & 1) res = res * a % mod;
    }
    return res;
}
signed main() {
    // ios::sync_with_stdio(0);
    // cin.tie(0), cout.tie(0);
    mi26[0] = 1;
    for (int i = 1; i < maxn; i++)
        mi26[i] = mi26[i - 1] * 26 % mod;
    int inv26 = ksm(26, mod - 2);
    int n, m; cin >> n >> m;
    int ans1 = 1;
    for (int i = 1; i <= m; i++) {
        ans1 += mi26[i] * ((1 - ksm((1 - ksm(inv26, i) + mod) % mod, n) + mod) % mod) % mod;
        ans1 %= mod;
    }
    int ans2 = 1, tem = 1;
    for (int i = 1; i <= m; i++) {
        tem *= 26;
        if (tem < n) ans2 += tem, ans2 %= mod;
        else {
            ans2 += (m - i + 1) * n % mod;
            ans2 %= mod;
            break;
        }
    }
    cout << ans2 << " " << ans1 << "\n";
    system("pause");  // 交之前记得注释一下,关流的时候不会显示输出
    return 0;
}

G

队友写的,网络流
看到数据范围 103 就开始考虑可能是一些科技东西,就往网络流考虑了,然后发现那些限制刚好也很符合网络流,具体建图不太清楚,好像是先考虑一下打车钱,然后两个人一起付钱就可以建虚点啥的,最后看能不能跑满最大流,直接看代码吧。

代码

点击查看代码
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
#define pii pair<int, int>
#define ll long long
using namespace std;
const int N = 3e3+99;
const int mod = 998244353;
struct Edge{
    int v, nxt, w;
}e[N*10];
int head[N], cur[N], tot = 1, s, t, dist[N], gap[N], sz;
ll dfs(int u, ll rest) {
    if (u == t) return rest;
    ll sum = 0;
    for (int &i = cur[u]; i; i = e[i].nxt) {
        if (dist[u] == dist[e[i].v] + 1 && e[i].w) {
            ll ret = dfs(e[i].v, min(rest, e[i].w));
            rest -= ret;
            sum += ret;
            e[i].w -= ret;
            e[i ^ 1].w += ret;
            if (rest == 0) return sum;
        }
    }
    gap[dist[u]]--;
    if (gap[dist[u]] == 0) dist[s] = sz + 1;
    dist[u]++;
    gap[dist[u]]++;
    cur[u] = head[u];
    return sum;
}
ll isap() {
    gap[0] = sz;
    ll ans = 0;
    while (dist[s] < sz) ans += dfs(s, inf);
    return ans;
}
void add(int u, int v, int w, int t = 1){
    e[++tot] = (Edge){v, head[u], w};
    head[u] = tot;
    if(t) add(v, u, 0, 0);
}
int a[N], val[N];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        cin >> a[i] >> val[i];
    s = n + m + 1, t = s + 1;
    sz = t;
    int sum = 0, flow = 0;    
    for(int i = 1; i <= m; i++){
        int x, y, w;
        cin >> x >> y >> w;
        add(x, n + i, inf);
        add(y, n + i, inf);
        add(n + i, t, w);
        flow += w;
        if(x == 1 || y == 1) sum += w;
    }
    sum = min(a[1], sum + val[1]);
    bool flag = 1;
    add(s, 1, sum - val[1]);
    for(int i = 2; i <= n; i++){
        a[i] = min(a[i], sum-1);
        if(a[i] < val[i]){
            flag = 0;
            break;
        }
        add(s, i, a[i] - val[i]);
    }
    if(!flag || isap() < flow)
        cout << "NO\n"; 
    else
        cout << "YES\n";
    //system("pause");  // 交之前记得注释一下,关流的时候不会显示输出
    return 0;
}

I

队友写的,是个三次方的dp,好像是预处理一个数组 ans[i] 表示拿到行李时间大等于 i 的方案数,这个数组是用三方的dp来求的,最后就可以差分得出每个时间的方案数,再求和啥的。

代码

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10;
const int M = 998244353;
signed main() {
    // ios::sync_with_stdio(0);
    // cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    int a[n + 1]{};  // xingli
    int b[m + 1]{};  // ren
    int ans[606]{};
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= m; i++)
        cin >> b[i];
    sort(a + 1, a + 1 + n);
    sort(b + 1, b + 1 + m);
    for (int k = 0; k <= 500; k++) {
        int dp[505][505]{};
        dp[0][0] = 1;
        int a1[n + 1]{};
        int pre[1050]{};
        for (int i = 1; i <= n; i++)
            a1[i] = a[i] + k, pre[a1[i]]++;
        for (int i = 1; i <= 500; i++)
            pre[i] += pre[i - 1];

        for (int j = 1; j <= m; j++)
            for (int i = 0; i <= 500; i++) {
                if (i - pre[b[j]] + pre[b[j - 1]] >= 0)
                    dp[i][j] += dp[i - pre[b[j]] + pre[b[j - 1]]][j - 1];
                if (i - pre[b[j]] + pre[b[j - 1]] + 1 >= 0)
                    dp[i][j] += dp[i - pre[b[j]] + pre[b[j - 1]] + 1][j - 1] *
                                (i + 1) % M;
                dp[i][j] %= M;
            }
        for (int i = 0; i <= n; i++)
            ans[k] += dp[i][m], ans[k] %= M;
    }
    int res = 0;

    for (int i = 1; i <= 500; i++) {
        res += (ans[i - 1] - ans[i] + M) % M * (i - 1) % M;
        res %= M;
    }
    cout << res;
    // system("pause");  // 交之前记得注释一下,关流的时候不会显示输出
    return 0;
}

J

和队友一起讨论出来,最后我写的,首先转化题意
两个数组分别异或,得到 sum1 和 sum2,容易发现如果交换 aibisum1sum1a[i]b[i],sum2同理,所以题意转化为,有两个数sum1 和 sum2,还有 n个数 aibi 你可以任意选择这 n 个数的若干个数,让 sum1 和 sum2同时异或上它们,问 max(sum1,sum2) 的最小值是多少。
由于是任意选择若干个数异或,显然可以考虑线性基,然后从高到低按位贪心考虑。如果sum1 和sum2这一位都是0,则不操作,若这一位都是1,则去异或线性基的这一位。难点在于一个1一个0的情况,这时候选不选择去异或,都不影响答案的这一位,但是选择异或,可能会对后面的位产生影响。
这时候一开始我有个错误的思路,碰到这样的数,我就不去异或,而是把线性基里的对应数去掉当前位,重新插入线性基。然而wa了之后队友发现这样假了,随便比如111和000,假如线性基里都有数,那么我就都不操作了,而明显可以通过交换这两个数的这一位可以使较大的数减小一些。
所以思路就有了,遇到一个0一个1的情况,可以通过异或线性基的这一位,交换这两个数的这一位。那么该怎么交换合理,又要考虑前面的位,如果已经有不一样的,那么大的数肯定一直都大(因为二进制下,从高到低第一个不同的位决定了数的大小),所以后面可以选择交换0、1的地方,就把1都分配给较小的数,所以后面这样的决策都是确定的。如果前面的位都一样呢?这里我是直接比较暴力的,把异或和不异或两种情况都跑一次,取个最小值,因为这一位开始,后面的决策也都确定,所以跑2次复杂度也是正确的。

代码

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define ll long long
using namespace std;
const int maxn = 1e6 + 10;
const int mod = 998244353;
int p[33];
int a[maxn], b[maxn];
void insert(int x) {
    for (int i = 31; i >= 0; i--) {
        if ((x >> i) == 0) continue;
        if (!p[i]) {
            p[i] = x;
            break;
        } else x = x ^ p[i];
    }
}
int dfs(int pos, int sum1, int sum2) {
    if (pos == -1) return max(sum1, sum2);
    int x1 = ((sum1 >> pos) & 1);
    int x2 = ((sum2 >> pos) & 1);
    if (x1 == 0 && x2 == 0) return dfs(pos - 1, sum1, sum2);
    if (x1 == 1 && x2 == 1) return dfs(pos - 1, sum1 ^ p[pos], sum2 ^ p[pos]);
    if ((x1 == 1 && sum1 > sum2) || (x2 == 1 && sum2 > sum1)) {
        return dfs(pos - 1, sum1 ^ p[pos], sum2 ^ p[pos]);
    } else {
        return dfs(pos - 1, sum1, sum2);
    }
}
void solve() {
    memset(p, 0, sizeof p);
    int n; cin >> n;
    int sum1 = 0, sum2 = 0;
    for (int i = 1; i <= n; i++) cin >> a[i], sum1 ^= a[i];
    for (int i = 1; i <= n; i++) cin >> b[i], sum2 ^= b[i];
    for (int i = 1; i <= n; i++) {
        insert(a[i] ^ b[i]);
    }
    for (int i = 31; i >= 0; i--) {
        int x1 = ((sum1 >> i) & 1);
        int x2 = ((sum2 >> i) & 1);
        if (x1 == 0 && x2 == 0) continue;
        if (x1 == 1 && x2 == 1) sum1 ^= p[i], sum2 ^= p[i];
        else {
            if ((sum1 >> (i + 1)) != (sum2 >> (i + 1))) {
                if ((x1 == 1 && sum1 > sum2) || (x2 == 1 && sum2 > sum1)) {
                    cout << dfs(i - 1, sum1 ^ p[i], sum2 ^ p[i]) << "\n";
                } else {
                    cout << dfs(i - 1, sum1, sum2) << "\n";
                }
                return;
            }
            else {
                int ans = min(dfs(i - 1, sum1, sum2), dfs(i - 1, sum1 ^ p[i], sum2 ^ p[i]));
                cout << ans << "\n";
                return;
            }
        }
    }
    cout << max(sum1, sum2) << "\n";
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int t; cin >> t;
    while (t--)
        solve();
    // system("pause");  // 交之前记得注释一下,关流的时候不会显示输出
    return 0;
}

K

签到,首先容易发现,奇数,先手肯定取1,之后就都只能拿1,先手胜。拓展一下就可以考虑所有 n 的因数,如果有奇数个它,那么先手就取这个数,后手无论怎么选,先手都可以取一样的数模仿。然后考虑哪个因数使得 n / 它是奇数,发现似乎是2的若干次幂,进而发现是 lowbit(n),所以最后只要判断一下 lowbit(n)k 的大小,就可以直接输出了。

代码

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10;
const int mod = 998244353;
void solve() {
    int n, k; cin >> n >> k;
    if ((n & (-n)) <= k)
        cout << "Alice\n";
    else
        cout << "Bob\n";
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int t; cin >> t;
    while (t--)
        solve();
    // system("pause");  // 交之前记得注释一下,关流的时候不会显示输出
    return 0;
}

L

纯模拟,暴力找,没什么好说的

代码

点击查看代码
#include <bits/stdc++.h>
// #define int long long
#define pii pair<int, int>
#define ll long long
using namespace std;
const int maxn = 5e2 + 10;
const int mod = 998244353;
char ch[maxn][maxn];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n, m; cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            cin >> ch[i][j];
        }
    int ans = 0;
    for (int i = 1; i < n; i++)
        for (int j = 1; j < m; j++) {
            if (ch[i][j] == 'c' && ch[i][j + 1] == 'c' && ch[i + 1][j] == 'p' && ch[i + 1][j + 1] == 'c')
                ans++;
        }
    cout << ans << endl;
    // system("pause");
    return 0;
}
posted @   TJUHuangTao  阅读(4346)  评论(4编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示