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;
}