把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

CSP-S2025 题目解析

看了我的游记的都知道我每道题目的做题情况。

T1

比较简单的签到题目。

你先贪心选每个数最大的那个组,然后判断有没有某一个组的大于 \(\frac{n}{2}\),显然这种组有且仅有一个。

我们考虑把这个组的某些数换组,那么到 \(\frac{n}{2}\) 就够了。

显然换到那个数的第二大的那个组即可,相减一下就是代价,直接用大根堆维护即可。

时间复杂度 \(\mathcal{O}(Tn\log n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#include <queue>
#define int long long
#define N 100005
using namespace std;
priority_queue<int> q[5];
int n;
struct node{
    int val,id;
}a[N][5];
signed main(){
    int T;
    cin >> T;
    for (;T--;) {
        for (int j = 1;j <= 3;j ++) {
            while(!q[j].empty()) q[j].pop();
        }
        scanf("%lld",&n);
        for (int i = 1;i <= n;i ++)
            for (int j = 1;j <= 3;j ++) scanf("%lld",&a[i][j].val),a[i][j].id = j;
        int ans = 0;
        for (int i = 1;i <= n;i ++) {
            stable_sort(a[i] + 1,a[i] + 1 + 3,[](node x,node y) {
                return x.val > y.val;
            });
            ans += a[i][1].val;
            q[a[i][1].id].push(a[i][2].val - a[i][1].val);
        }
        // cout << ans << '\n';
        bool flag = 0;
        for (int i = 1;i <= 3;i ++)
            flag |= (q[i].size() > n / 2);
        if (!flag) {
            printf("%lld\n",ans);
            continue;
        }
        int mxid = 1;
        for (int i = 2;i <= 3;i ++)
            if (q[i].size() > n / 2) {
                mxid = i;
                break;
            }
        // cout << mxid << '\n';
        while(q[mxid].size() > n / 2) {
            int t = q[mxid].top();
            ans += t;
            q[mxid].pop();
        }
        printf("%lld\n",ans);
    }
    system("pause");
    return 0;
}

T2

并未做出来,原因:误认为 \(2^k\leq 1.5\times 10^6\),就是 \(2^{20}\) 的那个上限,然后就不会了。

但其实显然 \(m\) 是诈骗,压成 \(n-1\) 条边就行了,于是可以得到本题性价比极高的 \(80pts\) 做法:\(\mathcal{O}(2^k(nk)\log (nk))\),感觉 \(CCF\) 现在换成 \(\text{Cure Ultra9}\) 可能能过。

然后你把排序换到外面去就能通过本题了。

再说一说我考场上的思路为什么错:每个城镇贡献一个完全图,这样的连边方式是错误的,因为会多算很多次 \(c\)(但是只没有通过一个大样例就很奇妙)。

T3

赛场上应该是可以切掉的,但是由于 \(T_2\) 没有通过大样例的心理施压导致没有把心思放到这上面来。

CCF 的题面提示我们可以将一个替换操作和被替换询问变成:前缀相同+第一个字符串不同的+第二个字符串不同的+后缀相同的,中间用空格隔开。

我们关心的只是不同的情况,对于前缀和后缀我们只需要判断是不是当前询问的前缀相同的后缀且是其后缀相同的前缀(可能有一点绕,请细细品位),然后肯定可以用 trie树 或者 AC自动机 维护,但是我想先验证一下,打了一个hash。

打了很久,这是交在洛谷只有 \(10pts\)赛场复刻代码,不忍直视:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#include <map>
#define int long long
#define uint unsigned long long
#define N 200005
#define M 5500005
#define PII pair<int,int>
using namespace std;
int n,q;
uint Pow[M],seed = 231;
map<uint,int> mp;
map<uint,vector<PII> >mp2;
signed main(){
    Pow[0] = 1;
    for (int i = 1;i < M;i ++) Pow[i] = Pow[i - 1] * seed;
    cin >> n >> q;
    for (int i = 1;i <= n;i ++) {
        string s1,s2;
        cin >> s1 >> s2;
        int st = 0;
        while(s1[st] == s2[st]) st ++;
        int ed = s1.size() - 1;
        while(s1[ed] == s2[ed]) ed --;
        uint h = 0,hh = 0;
        for (int i = 0;i < st;i ++) h = h * seed + s1[i];
        h = h * seed + ' ';
        for (int i = st;i <= ed;i ++) h = h * seed + s1[i],hh = hh * seed + s1[i];
        h = h * seed + ' ';
        hh = hh * seed + ' ';
        for (int i = st;i <= ed;i ++) h = h * seed + s2[i],hh = hh * seed + s2[i];
        h = h * seed + ' ';
        for (int i = ed + 1;i < s1.size();i ++) h = h * seed + s1[i];
        mp[h] ++;
        mp2[hh].push_back({st,(int)s1.size() - ed - 1});
        // cout << h << ' ' << hh << '\n';
    }
    for (string t1,t2;q --;) {
        cin >> t1 >> t2;
        if (t1.size() != t2.size()) {
            puts("0");
            continue;
        }
        int st = 0;
        while(t1[st] == t2[st]) st ++;
        int ed = t1.size() - 1;
        while(t1[ed] == t2[ed]) ed --;
        string s = "";
        for (int i = 0;i < st;i ++) s += t1[i];
        s += " ";
        uint h2 = 0;
        for (int i = st;i <= ed;i ++) s += t1[i],h2 = h2 * seed + t1[i];
        s += " ";
        h2 = h2 * seed + ' ';
        for (int i = st;i <= ed;i ++) s += t2[i],h2 = h2 * seed + t2[i];
        s += " ";
        for (int i = ed + 1;i < t1.size();i ++) s += t1[i];
        vector<uint> hh;
        uint h = 0;
        for (auto i : s) h = h * seed + i,hh.push_back(h);
        int ans = 0;
        for (auto i : mp2[h2]) {
            int l = st - i.first,r = st + (2 * (ed - st + 2)) + i.second;
            // cout << i.first << '-' << i.second << '\n';
            if (l < 0 || r > s.size()) continue;
            // cout << l << ' ' << r << '\n';
            uint hsh = 0;
            // for (int j = l;j <= r;j ++) hsh = hsh * seed + s[j];
            if (l == 0) hsh = hh[r];
            else hsh = hh[r] - hh[l - 1] * Pow[r - l + 1];
            // cout << hsh << '\n';
            ans += mp[hsh];
        }
        printf("%lld\n",ans);
    }
    // system("pause");
    return 0;
}

样例 \(3\) 是通过不了的,输出的比他多。

你看出来是哪里多了吗?

没错!

由于每一对长度可能相同,但是方案只有这种类型的串的个数,因此我们去重一下就行了,就变成了:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#include <map>
#define int long long
#define uint unsigned long long
#define N 200005
#define M 5500005
#define PII pair<int,int>
using namespace std;
int n,q;
uint Pow[M],seed = 231;
map<uint,int> mp;
map<uint,vector<PII> >mp2;
signed main(){
    Pow[0] = 1;
    for (int i = 1;i < M;i ++) Pow[i] = Pow[i - 1] * seed;
    cin >> n >> q;
    for (int i = 1;i <= n;i ++) {
        string s1,s2;
        cin >> s1 >> s2;
        int st = 0;
        while(s1[st] == s2[st]) st ++;
        int ed = s1.size() - 1;
        while(s1[ed] == s2[ed]) ed --;
        uint h = 0,hh = 0;
        for (int i = 0;i < st;i ++) h = h * seed + s1[i];
        h = h * seed + ' ';
        for (int i = st;i <= ed;i ++) h = h * seed + s1[i],hh = hh * seed + s1[i];
        h = h * seed + ' ';
        hh = hh * seed + ' ';
        for (int i = st;i <= ed;i ++) h = h * seed + s2[i],hh = hh * seed + s2[i];
        h = h * seed + ' ';
        for (int i = ed + 1;i < s1.size();i ++) h = h * seed + s1[i];
        mp[h] ++;
        mp2[hh].push_back({st,(int)s1.size() - ed - 1});
        // cout << h << ' ' << hh << '\n';
    }
    for (auto i : mp2) {//多了这一段代码
        sort(i.second.begin(),i.second.end());
        i.second.erase(unique(i.second.begin(),i.second.end()),i.second.end());
        mp2[i.first] = i.second;//注意这个,赛场没有写这个所以没有减枝优化
    }
    for (string t1,t2;q --;) {
        cin >> t1 >> t2;
        if (t1.size() != t2.size()) {
            puts("0");
            continue;
        }
        int st = 0;
        while(t1[st] == t2[st]) st ++;
        int ed = t1.size() - 1;
        while(t1[ed] == t2[ed]) ed --;
        string s = "";
        for (int i = 0;i < st;i ++) s += t1[i];
        s += " ";
        uint h2 = 0;
        for (int i = st;i <= ed;i ++) s += t1[i],h2 = h2 * seed + t1[i];
        s += " ";
        h2 = h2 * seed + ' ';
        for (int i = st;i <= ed;i ++) s += t2[i],h2 = h2 * seed + t2[i];
        s += " ";
        for (int i = ed + 1;i < t1.size();i ++) s += t1[i];
        vector<uint> hh;
        uint h = 0;
        for (auto i : s) h = h * seed + i,hh.push_back(h);
        int ans = 0;
        for (auto i : mp2[h2]) {
            int l = st - i.first,r = st + (2 * (ed - st + 2)) + i.second;
            if (l < 0) break;//排序后的减枝
            // cout << i.first << '-' << i.second << '\n';
            if (l < 0 || r >= s.size()) continue;
            // cout << l << ' ' << r << '\n';
            uint hsh = 0;
            // for (int j = l;j <= r;j ++) hsh = hsh * seed + s[j];
            if (l == 0) hsh = hh[r];
            else hsh = hh[r] - hh[l - 1] * Pow[r - l + 1];
            // cout << hsh << '\n';
            ans += mp[hsh];
        }
        printf("%lld\n",ans);
    }
    // system("pause");
    return 0;
}

感觉这个要卡还是能卡的(要卡常),不过在随机数据下面是比较好的,如果刻意去卡的话就是构造一个类似:

 b c 
b c ?
? b c 
....

询问就是都询问 \(b\) 换成 \(c\) 的方案嘛,由于我们注意到其替换方案的字符串总和 \(\leq 5\times 10^6\)

那么相当于:

\[\sum_{i=0}^x i(i+1)\leq 5\times 10^6 \]

计算出来 \(x\) 约等于 \(245\)

所以说查询的时候枚举为 \(\sum_{i=0}^x (i+1)=29890\)

应该加一个记忆化就能过。

差不多是 \(\mathcal{O}(\sum|s_i|+\sum|t_i|+q\sqrt{\sum|s_i|})\)。当然比这个大。

比较极限,但是应该可以过。

但是我们的构造方式相当于跑一个子串即可,直接 \(AC\) 自动机就行了,看来这道题目就是道蓝题,只不过自己调hash有点久了。

T4

这题挺秒的。

我们想这个道题目重点就在于他出简单题的情况,因为出简单题有些人能做出来而有些不行,这就很烦。

烦怎么办?容斥啊!

\(sum\) 表示前缀没有做出来的总人数。

考虑当天的情况:

  • \(s_i=0\),那么显然,随便放人就行了。
  • \(s_i=1\),如果耐心比这个大就可以做出来,否则不行。

这个耐心比这个大是不是很烦,容斥成任意减掉小于等于的情况。

故设 \(f_{i,j,k}\) 表示前 \(i\) 个人有 \(j\) 个没有做出来,钦定了 \(k\) 个人被限制了的方案。

转移(只考虑 \(s_{i+1}=1\)):

  • \(f_{i+1,j,k}\leftarrow f_{i,j,k}\),这个是直接做出来。
  • \(f_{i+1,j,k+1}\leftarrow -x\times f_{i,j,k}\),这个是容斥。
  • \(f_{i+1,j+1,k+1}\leftarrow x\times f_{i,j,k}\),这个是多钦定一个人没有做出来这道题目。

然后最后的答案就是 \(\sum_{i=0}^{n-m}\sum_{j=0}^nf_{n,i,j}\times(n-k)!\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#define int long long
#define N 505
using namespace std;
const int mod = 998244353;
int n,m,p[N],c[N],cnt[N];
char s[N];
int f[2][N][N],jc[N];
signed main(){
    jc[0] = jc[1] = 1;
    for (int i = 2;i < N;i ++) jc[i] = jc[i - 1] * i % mod;
    cin >> n >> m;
    scanf("%s",s + 1);
    int mx = 0;
    for (int i = 1;i <= n;i ++) scanf("%lld",&c[i]),p[i] = i,cnt[c[i]] ++;
    for (int i = 1;i <= n;i ++) cnt[i] += cnt[i - 1];
    f[0][0][0] = 1;
    for (int i = 0,now = 0,nxt = 1;i < n;i ++,swap(now,nxt)) {
        for (int j = 0;j <= n;j ++)
            for (int k = 0;k <= n;k ++) f[nxt][j][k] = 0;
        if (s[i + 1] == '0') {
            for (int j = 0;j <= i;j ++)
                for (int k = 0;k <= i;k ++) f[nxt][j + 1][k] += f[now][j][k],f[nxt][j + 1][k] %= mod;
        }
        else 
            for (int j = 0;j <= i;j ++)
                for (int k = 0;k <= i;k ++) {
                    f[nxt][j][k] += f[now][j][k];
                    f[nxt][j][k] %= mod;
                    int w = (cnt[j] - k);
                    f[nxt][j][k + 1] -= w * f[now][j][k] % mod;
                    f[nxt][j][k + 1] = (f[nxt][j][k + 1] + mod) % mod;
                    f[nxt][j + 1][k + 1] += w * f[now][j][k] % mod;
                    f[nxt][j + 1][k + 1] %= mod;
                }
    }
    int ans = 0;
    for (int j = 0;j <= n - m;j ++)
        for (int k = 0;k <= n;k ++) ans = (ans + f[n & 1][j][k] * jc[n - k] % mod) % mod;
    printf("%lld",ans);
    system("pause");
    return 0;
}
posted @ 2025-11-02 22:03  high_skyy  阅读(371)  评论(1)    收藏  举报
浏览器标题切换
浏览器标题切换end