0%

[ARC153E] Deque Minimization

后面的部分想了好久,今天画一画突然就明白了。

题目链接。

题意

对于一个各位均不为 的数字 ,定义 为如下操作能得到的最小的 :

对于 十进制下的每一位,从高到低依次将其放到 的最左边或最右边。

给出 ,问有多少 ,对 取模。

。

题解

令 ,则 。

先考虑对于一个 来说,最小的 是多少。

观察 的结构,发现他可以分为 部分,第一部分是 按照从右向左的顺序拿一些位出来。

然后第二部分是 的最高位,第三部分是 剩下的位,从左到右拿出来。

思考可以发现, 的位数是固定的,所以首先需要让 的最高位最小,所以 的最高位一定是 每一位最小值,这些位一定在上述所说的 的“第一部分”。

因此我们先将所有 的最小值全部归到 的第一部分去,考虑 的第一部分还剩下哪些元素。

容易发现,接下来一定填充的是可以拿的(这里可以拿的指, 第一个最小值左边的部分)部分中,所有的最小值。

比如 ,我们先将所有的 拿出来,此时剩下可以拿的部分是 ,此时我们应该拿 。

这样一直拿,直到拿到了第一个位置为止,容易发现我们一定构造出来了最小的 。

观察上述过程,发现其实与这个过程等价:

首先将 的第一位加入队列中,然后对于后面的每一位,若它是前缀最小值之一,将这一位放进队首;否则将其放进队尾。

设 为最大的位置,使得 不降。那么我们知道 的第一位一定是 中的一个。

考虑使用 表示有多少种数字 ,对应的 是 。

边界状态是 ,其中 。

考虑转移,首先可以发现 一定可以转移到 (提醒:此时一定有 )。

然后可以发现若 ,则 还可以转移到 。

这样就得到了一个 的做法。

写一下可以发现没有假。


考虑优化这个 dp,可以发现 是自增的。

也就是说对于两行之间,这个 dp 本质上只有 种转移。

而相同的转移内部类似于一个网格图,可以用 NTT 优化转移。

时间复杂度为 。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fll
#define debug(x) cerr<<#x<<"="<<x<<endl
using namespace std;
using ll=long long;
using ld=long double;
using pli=pair<ll,int>;
using pi=pair<int,int>;
template<typename A>
using vc=vector<A>;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
inline ll lread()
{
ll s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int mod=998244353;
const int G=3;
const int NG=(mod+1)/G;
int wh[530001];
ll f[530001];
ll g[530001];
ll jc[400005];
ll inv[400005];
ll dp[200005];
char s[200005];
int n;int L;
inline void Add(ll &a,ll b)
{
a+=b;
if(a>=mod) a-=mod;
}
inline ll qow(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
inline ll C(int a,int b)
{
if(a<b||b<0) return 0;
return jc[a]*inv[b]%mod*inv[a-b]%mod;
}
inline void init(int L)
{
jc[0]=inv[0]=1;
for(int i=1;i<=L;i++)
{
jc[i]=jc[i-1]*i%mod;
inv[i]=qow(jc[i],mod-2);
}
}
inline void NTT(ll *f,bool dft=true)
{
for(int i=0;i<(1<<L);i++) if(i<wh[i]) swap(f[i],f[wh[i]]);
for(int len=1;len<=L;len++)
{
ll step=qow(dft?G:NG,(mod+1)/(1<<len));int P=1<<len;
for(int l=0,mid=P>>1,r=P;l<(1<<L);l=r,r+=P,mid+=P)
{
ll now=1;
for(int i=l,j=mid;j<r;i++,j++)
{
ll num=f[j]*now%mod;
f[j]=(f[i]-num+mod)%mod;
f[i]=(f[i]+num)%mod;
now=now*step%mod;
}

}
}
if(!dft)
{
ll num=qow(1<<L,mod-2);
for(int i=0;i<(1<<L);i++) f[i]=f[i]*num%mod;
}
}
inline void NTT()
{
NTT(f),NTT(g);
for(int i=0;i<(1<<L);i++) f[i]=f[i]*g[i]%mod;
NTT(f,0);
}
int main()
{
scanf("%s",s+1),n=strlen(s+1);int k=1;
while(k<n&&s[k]<=s[k+1]) k++;

dp[k]=1;int r=k;
while(k>1&&s[k]==s[k-1]) k--,dp[k]=1;
init(2*n);
while((1<<L)<=2*n) L++;
for(int i=0;i<(1<<L);i++) wh[i]=(wh[i>>1]>>1)|((i&1)?(1<<(L-1)):0);
while(k!=1)
{
k--,dp[k]=1;
for(int i=k;i<n;i++) if(s[i+1]>s[k]) Add(dp[i+1],dp[i]);

int mem=k;
while(k>1&&s[k]==s[k-1]) k--,dp[k]=1;

//对 mem ~ r 做卷积
while(r<n&&s[r+1]>s[k]) r++;
if(mem!=k)
{
for(int i=mem;i<=r;i++) f[i]=dp[i];
for(int i=0;i<=n;i++) g[i]=C(i+mem-k-1,i);
NTT();
for(int i=mem;i<=r;i++) dp[i]=f[i];
}
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
}
printf("%lld\n",dp[n]);
return 0;
}