ABC234 解题题解

综述

自己真的是非常菜,ABC 的题其实并没有什么难度,只是自己太菜了。
由于篇幅原因,以下只说后三道题的题解。

F

F 题是一道计数题,首先不难发现的是,最终答案和每一个字符出现的次数有关系。

一开始并没有一个很好的思路,我们不妨把问题缩小。

如果只有一个字符,答案是很简单的。

如果有两个字符呢,我们不放认为我们先放第一个字母,然后再放第二个字符。于是我们就有了一个很自然的思路,设 \(f_i\) 为长度为 \(i\) 的字符串的组成个数,我们首先考虑先只放第一个字符,然后考虑如何放第二个字符。

然后我们发现,如果原字符串长度确定了,你要放的第二个字符的个数确定了,那么把这些字符往里放就是一个很简单的相同球放不同盒子允许空的问题,可以用组合数进行表示。

然后转移就非常简单:\(f_{j+k}\leftarrow f_j\times C\) 后面那个 \(C\) 是一个组合数.我们考虑这样是否会算重,显然不会,因为要么字符串原长不一样,要么放进去的第二个字符数量不同。

不难发现我们可以把这种做法推广到 \(26\) 个字母,只需要进行 \(26\) 次迭代。

注意实现的时候需要开一个辅助数组,帮助记录新的值,然后我们再加回去。


#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 5010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=998244353;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int ch[26],f[N],Jie[N],Jieinv[N],Inv[N],len,g[N],Ans;
char s[N];

inline int C(int n,int m){
    if(n<m) return 0;
    return 1ll*Jie[n]*Jieinv[m]%mod*Jieinv[n-m]%mod;
}

int main(){
    scanf("%s",s+1);len=strlen(s+1);
    for(int i=1;i<=len;i++){
        ch[s[i]-'a']++;
    }
    Jie[0]=1;for(int i=1;i<=5000;i++) Jie[i]=1ll*Jie[i-1]*i%mod;
    Inv[1]=1;for(int i=2;i<=5000;i++) Inv[i]=1ll*(mod-mod/i)*Inv[mod%i]%mod;
    Jieinv[0]=1;for(int i=1;i<=5000;i++) Jieinv[i]=1ll*Jieinv[i-1]*Inv[i]%mod;
    f[0]=1;
    for(int i=0;i<26;i++){
        for(int j=0;j<=len;j++){
            for(int k=1;k<=ch[i]&&k+j<=len;k++){
                g[k+j]=(g[k+j]+1ll*f[j]*C(j+k,k)%mod)%mod;
            }
        }
        for(int j=0;j<=len;j++) f[j]=(f[j]+g[j])%mod;
        for(int j=0;j<=len;j++) g[j]=0;
    }
    for(int i=1;i<=len;i++) Ans=(Ans+f[i])%mod;
    // for(int i=1;i<=len;i++) printf("%d ",f[i]);puts("");
    printf("%d\n",Ans);
    return 0;
}

G

这个题看起来比较令人头大,但实际上只要看出是一个 dp 的本质,这个题就不难做。

如何看出是一个 dp 呢,其实用 dp 可以解决许多这样的划分问题,我一开始还以为是 CDQ 分治之类的东西,其实不是。

我们设 \(f_i\) 表示把前 \(i\) 个元素按照题目所示方法划分得到的答案,转移方程不难得到:

\[ f_i=\sum\limits_{j=0}^{i-1}f_j(\max\{ A_{j+1},...A_i \}-\min\{ A_{j+1},...A_i \}) \]

不难发现朴素的复杂度是 \(O(n^2)\) 的,考虑如何优化。

遇到最值,我们首先考虑可以去掉冗余信息的单调栈和单调队列。

不难发现这个东西可以用单调栈来进行优化。代码实现细节较多,要注意下标。


#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 300010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=998244353;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int n,a[N],f[N];
int m1[N],t1,m2[N],m3[N],t3,m4[N],maxx,minn;

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);for(int i=1;i<=n;i++) read(a[i]);
    f[0]=1;
    for(int i=1;i<=n;i++){
        int sum1=0,sum2=0;
        while(t1>0&&m1[t1]<=a[i]){
            (maxx-=1ll*m1[t1]*m2[t1]%mod)%=mod;
            (sum1+=m2[t1])%=mod;t1--;
        }
        t1++;m1[t1]=a[i];m2[t1]=(sum1+f[i-1])%mod;
        (maxx+=1ll*m1[t1]*m2[t1]%mod)%=mod;
        while(t3>0&&m3[t3]>=a[i]){
            (minn-=1ll*m3[t3]*m4[t3]%mod)%=mod;
            (sum2+=m4[t3])%=mod;t3--;
        }
        t3++;m3[t3]=a[i];m4[t3]=(sum2+f[i-1])%mod;
        (minn+=1ll*m3[t3]*m4[t3]%mod)%=mod;
        // printf("%d %d\n",maxx,minn);
        f[i]=(maxx-minn)%mod;
    }
    printf("%d\n",(f[n]%mod+mod)%mod);
    return 0;
}

Ex

这个题非常巧妙地用到了坐标系的放缩,这种方法在之前不曾见过,非常巧妙的想法。
具体的思路是这样的:我们把每一个点的坐标都变成除以 \(k\) 下取整,这样,对于放缩后在点 \((a,b)\) 的点来说,可能能够成为答案的也就是以这个点为中心的九宫格,其余点都不可能成为答案。

我们现在需要简单证明一下这个东西的复杂度是 \(O(n+m)\) 的。

我们考虑一下,设 \(f(x)\) 为在一个点中有 \(x\) 个点,满足题意的点对数,显然 \(f(x)=x^2\)。

现在我们考虑一下,根据题意,一定有每一个点的 \(f\) 值加起来满足为 \(O(n+m)\),设 \(B_{x,y}\) 为在 \((x,y)\) 这个点的点数,我们考虑一下我们实际上的复杂度为:

\[ \sum\limits_{X,Y}\sum\limits_{j=-1}^{1}\sum\limits_{k=-1}^{1}B_{X,Y}B_{X+j,Y+k} \]

我们有:\(B_{X,Y}B_{X+j,Y+k} \le \frac 12 (B_{X,Y}^2+B_{X+j,Y+k}^2)\)

实际上,根据我们上面所说的 \(\sum\limits_{X,Y}f(B_{X,Y})=\sum\limits_{X,Y}B_{X,Y}^2=O(N+M)\)

可以知道整个过程的时间复杂度大约为 \(O(N+M)\)

代码:


#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 300010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=998244353;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int n,a[N],f[N];
int m1[N],t1,m2[N],m3[N],t3,m4[N],maxx,minn;

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);for(int i=1;i<=n;i++) read(a[i]);
    f[0]=1;
    for(int i=1;i<=n;i++){
        int sum1=0,sum2=0;
        while(t1>0&&m1[t1]<=a[i]){
            (maxx-=1ll*m1[t1]*m2[t1]%mod)%=mod;
            (sum1+=m2[t1])%=mod;t1--;
        }
        t1++;m1[t1]=a[i];m2[t1]=(sum1+f[i-1])%mod;
        (maxx+=1ll*m1[t1]*m2[t1]%mod)%=mod;
        while(t3>0&&m3[t3]>=a[i]){
            (minn-=1ll*m3[t3]*m4[t3]%mod)%=mod;
            (sum2+=m4[t3])%=mod;t3--;
        }
        t3++;m3[t3]=a[i];m4[t3]=(sum2+f[i-1])%mod;
        (minn+=1ll*m3[t3]*m4[t3]%mod)%=mod;
        // printf("%d %d\n",maxx,minn);
        f[i]=(maxx-minn)%mod;
    }
    printf("%d\n",(f[n]%mod+mod)%mod);
    return 0;
}

上一篇:华中农业大学数学实验期末考试答案(matlab)


下一篇:信息系统项目管理03——项目立项管理