「模拟赛」多校 A 层冲刺 NOIP 24

总结

T1 各种做法迷乱了我的心智,带上推 KMP 的半小时总共调了 2.5 h T1,浪费时间太长了,导致最后 T2、T4 暴力都没打,打了就 Rank 4 了

中间看题 +【数据删除】 用了半小时

T2 简单推了二十多分钟觉得没前途先开了 T3,一眼会思路,感觉有点小细节,决定先打暴力再打倍增优化,结果 \(n^2\) 一打就打了一小时,离比赛结束就剩 5 分钟了,于是直接摆了

赛后改完 T3 的倍增:我去还好赛时没打出来,要是打出来了就全 MLE 了

明天一定打暴力!!!

A.选取字符串

KMP、字符串好题

因为所有字符串都是大字符串的前缀,所以一旦我们每个字符串的前缀后缀的长度确定了,那么前缀后缀长什么样也就确定了

\(f_i\) 为所有相同前缀后缀长度可以为 \(i\) 的字符串的个数

我们枚举 \(i\in [1,n]\),每次钦定两个串 \(p、q\) 里必须有一个是 \(S_i\),而另一个串可以在合法的范围内任意选。

\(g_i\) 表示必选前缀串 \(S_i\) 时另一个串可以选的字符串的个数

那么答案就是 \(\sum_{i=1}^n \binom {f_i}{k}\times (2\times g_i-1)\) + \(\binom {n+1}{k}\)

Why:另一个串有 \(g_i\) 种可能,而 \(p、q\) 的顺序不同为不同的方案,所以乘 2,再减去 \(p、q\) 都选 \(S_i\) 的情况(这个情况 \(p、q\) 顺序不同也是一种方案)
最后 \(p、q\) 全选空串的方案单独算即为 \(n+1\) 个串里选 \(k\)

现在考虑 \(f_i、g_i\) 的计算:

  • 对于 \(f_i\):KMP 求出所有字符串 \(S_{[1,i]}\) 的 包的er 值 \(pi_i\),即最长的既是前缀也是后缀的长度,那么每个字符串 \(S_i\) 相同前缀后缀可以为 \(S_{[1,\ pi_i]}\),同时也可以为 \(S_{[1,\ pi_i]}\) 的前缀后缀,所以从大到小递推计算:f[pi[i]] += f[i];

  • 对于 \(g_i\):显然选 \(S_i\) 时同样可以为所有字符串的前缀后缀的只能是 \(S_i\) 的前缀后缀,也是同理递推得到,g[i] += g[pi[i]];

code
#include<bits/stdc++.h>
#define int long long
#define Aqrfre(x, y) freopen(#x ".in", "r", stdin),freopen(#y ".out", "w", stdout)
#define mp make_pair
#define Type int
#define qr(x) x=read()
typedef long long ll;
using namespace std;

inline Type read(){
    char c=getchar(); Type x=0, f=1;
    while(!isdigit(c)) (c=='-'?f=-1:f=1), c=getchar();
    while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48), c=getchar();
    return x*f;
}

const int N = 1e6 + 5;
const int mod = 998244353;

inline vector<int> pretix(string s){
    int len = s.length();
    vector<int>v(len+10); v[0] = 0;
    for(int i=1; i<len; i++){
        int j = v[i-1]; //v: The length of the string which from 1 to i - 1
        while((i == j or s[i] != s[j]) and j) j = v[j-1];
        if(s[i] == s[j]) j++;
        v[i] = j;
    }
    return v;
}

string s;
int n, k, sum[N], f[N], g[N];
vector<int>pi;

int fact[N], inv[N], fainv[N];
inline void AqrPre(){
    fact[0] = fact[1] = 1;
    inv[0] = inv[1] = 1;
    fainv[0] = fainv[1] = 1;
    for(int i=2; i<=n+3; i++){
        fact[i] = 1ll * fact[i-1] * i % mod;
        inv[i] = 1ll * inv[mod%i] * (mod - mod / i) % mod;
        fainv[i] = 1ll * fainv[i-1] * inv[i] % mod;
    }
}

inline int C(int x, int y){
    return x < y ? 0 : 1ll * fact[x] * fainv[y] % mod * fainv[x-y] % mod;
}

/*
思路应该是显然的??实现也简单啊只不过我假了一版又一版

那这题确实是简单签到了啊,但我打了两个半小时??

还有救么??这让我怎么翻?后面三题只打暴力肯定不行吧
*/

signed main(){ // string
    Aqrfre(string, string);

    qr(k); cin>>s; n = s.length();
    AqrPre(); pi = pretix(s);

    for(int i=0; i<n; i++) f[pi[i]]++, g[i+1] = 1;
    for(int i=1; i<=n; i++) g[i] += g[pi[i-1]];

    for(int i=n; i>=1; i--)
        if(i and pi[i-1]) f[pi[i-1]] += f[i];

    int ans = 0;
    for(int i=1; i<=n; i++){
        ans += C(f[i]+1, k) * ((g[i] + 1) * 2 - 1) % mod;
        ans %= mod;
    }
    cout<<(ans+C(n+1, k))%mod<<"\n";



    return 0;
}

B.取石子

博弈论

搞不懂放博弈论的目的是什么,仙姑

C.均衡区间

二维数点

感觉思路比 T1 还简单啊

啥是二维数点啊我不会??挂 \(\log\) 是没前途的,所以提供一种不挂 \(\log\) 的目前最优解( 极限 \(n^2\) 的哈哈

\[------------------------------------------------------ \]

\(i\) 为左端点时举例:

\(Rmax_i\) 为右侧第一个大于 \(a_i\) 的位置,\(Rmin_i\) 同理

预处理出所有的 \(Rmax_i\) 以及 \(Rmin_i\),和 \(Ranum_i\) 表示从 \(i\) 开始递增子序列的长度,\(Rbnum_i\) 表示 \(i\) 开始递减子序列的长度

可以根据转移理解一下 \(Ranum,Rbnum\) 数组:Ranum[i] = Ranum[Rmax[i]] + 1;

发现从 \(max(Rmax, Rmin)\) 开始到最后,除了所有的比当前最大的还大的、比当前最小的还小的,其余都可以作为合法的右端点

统计答案:

我们把 \(Rmax_i、Rmin_i\) 里较小的一个不断跳到大于另一个的位置

\(Rmax_i<Rmin_i\) 时,不断赋值 \(r1 = Rmax_{r1}\ 直到\ r1 >= Rmin_i\)

这个时候答案就是 \((n-Rmin_i+1)-Ranum[Rmin_i]-Rbnum[r1]-2\)

code
#include<bits/stdc++.h>
#define Aqrfre(x, y) freopen(#x ".in", "r", stdin),freopen(#y ".out", "w", stdout)
#define mp make_pair
#define Type int
#define qr(x) x=read()
typedef long long ll;
using namespace std;

inline Type read(){
    char c=getchar(); Type x=0, f=1;
    while(!isdigit(c)) (c=='-'?f=-1:f=1), c=getchar();
    while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48), c=getchar();
    return x*f;
}

const int N = 1e6 + 5;
const int mod = 998244353;

/*
怎么 T3 也放一眼题??

以 i 为左端点举例:

预处理 i 右侧第一个比它大、小的数 Rmax[i], Rmin[i]

i+1 ~ min(Rmax[i], Rmin[i])-1 合法,假设是 Rmin 小一点

现在的 Rmin 变为 Rmin[Rmin[i]]

错了错了,反了!!

直接数卡后的比当前最大还大 ~,比当前最小还小 ~ 就做完了

丸辣!成小丑了,n^2 暴力调了一个小时,呜呜 ST 表胡不上了

还剩 3 分钟,摆了,拿多少分看命吧
*/

int n, a[N], id, L[N], R[N];
int Rmax[N], Rmin[N], Ranum[N], Rbnum[N];
int Lmax[N], Lmin[N], Lanum[N], Lbnum[N];
deque<int>qmax, qmin;

signed main(){ // interval
    Aqrfre(interval, interval);

    qr(n); qr(id);
    for(int i=1; i<=n; i++) qr(a[i]);

    for(int i=1; i<=n; i++){
        while(qmax.size() and a[qmax.front()] <= a[i]) Rmax[qmax.front()] = i, qmax.pop_front();
        while(qmin.size() and a[qmin.front()] >= a[i]) Rmin[qmin.front()] = i, qmin.pop_front();
        qmax.push_front(i); qmin.push_front(i);
    }

    while(qmax.size()) qmax.pop_front();
    while(qmin.size()) qmin.pop_front();

    for(int i=n; i>=1; i--){
        while(qmax.size() and a[qmax.front()] <= a[i]) Lmax[qmax.front()] = i, qmax.pop_front();
        while(qmin.size() and a[qmin.front()] >= a[i]) Lmin[qmin.front()] = i, qmin.pop_front();
        qmax.push_front(i); qmin.push_front(i);
    }

    for(int i=1; i<=n; i++){
        if(!Rmax[i]) Rmax[i] = n + 1;
        if(!Rmin[i]) Rmin[i] = n + 1;
        if(!Lmax[i]) Lmax[i] = 0;
        if(!Lmin[i]) Lmin[i] = 0;
    }

    Ranum[n+1] = Rbnum[n+1] = -1;
    for(int i=n; i>=1; i--){
        Ranum[i] = Ranum[Rmax[i]] + 1;
        Rbnum[i] = Rbnum[Rmin[i]] + 1;
        int r1 = Rmax[i], r2 = Rmin[i], cnt = 0;
        if(!Ranum[i] or !Rbnum[i]){
            R[i] = 0; continue;
        }
        if(r1 < r2){
            if(id == 2){ R[i] = 0; continue; }
            while(r1 < r2) r1 = Rmax[r1];
            cnt = Ranum[r1] + 2 + Rbnum[r2];
            cnt = n - r2 + 1 - cnt;
        }
        else{
            if(id == 2){ R[i] = 0; continue; }
            while(r2 < r1) r2 = Rmin[r2];
            cnt = Ranum[r1] + 2 + Rbnum[r2];
            cnt = n - r1 + 1 - cnt;
        }
        R[i] = cnt;
    }
    for(int i=1; i<=n; i++) cout<<R[i]<<" "; cout<<"\n";


    Lanum[0] = Lbnum[0] = -1;
    for(int i=1; i<=n; i++){
        Lanum[i] = Lanum[Lmax[i]] + 1;
        Lbnum[i] = Lbnum[Lmin[i]] + 1;
        int l1 = Lmax[i], l2 = Lmin[i], cnt = 0;
        if(!Lanum[i] or !Lbnum[i]){
            L[i] = 0; continue;
        }
        if(l1 > l2){
            if(id == 2){ L[i] = 0; continue; }
            while(l1 > l2) l1 = Lmax[l1];
            cnt = Lanum[l1] + 2 + Lbnum[l2];
            cnt = l2 - cnt;
        }
        else{
            if(id == 2){ L[i] = 0; continue; }
            while(l2 > l1) l2 = Lmin[l2];
            cnt = Lanum[l1] + 2 + Lbnum[l2];
            cnt = l1 - cnt;
        }
        L[i] = cnt;
    }
    for(int i=1; i<=n; i++) cout<<L[i]<<" ";


    return 0;
}

D.禁止套娃

posted @ 2024-11-20 07:43  Aqr_Rn  阅读(53)  评论(1编辑  收藏  举报