[硫化铂]Minimal DFA
Posted StaroForgin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[硫化铂]Minimal DFA相关的知识,希望对你有一定的参考价值。
Minimal DFA
题目概述
题解
首先,我们考虑当我们需要识别的字符串集合确定时,我们会如何构造出一个最小DFA。
可以发现,由于我们需要识别一个长度为
n
n
n的字符串,那么我们的
D
F
A
DFA
DFA必定是一个
n
n
n层的分层有向无环图。
不然的话,显然是不能保证我们识别出来的字符串的长度为
n
n
n的。
由于要将这个分层图最小化,那么我们必须将之后转移状态相同的点合并起来,也就是说,对于两个前缀,如果它们的后缀在集合中的状态相同,那么我们就可以将它们合作一个点,显然,这样构造得到的DFA一定是最小的。
那么怎么算最小状态数最大是多少呢?
我们可以记第
i
i
i层的点数为
d
i
d_i
di,显然可以得到这样两个大小关系的不等式:
d
i
+
1
⩽
2
d
i
d
i
⩽
(
d
i
+
1
+
1
)
2
−
1
\\left\\\\beginarrayccd_i+1\\leqslant 2d_i\\\\d_i\\leqslant (d_i+1+1)^2-1\\endarray\\right .
di+1⩽2didi⩽(di+1+1)2−1
第一个不等式说明的是我们的第
i
i
i层中每个点最多向第
i
+
1
i+1
i+1层延伸
2
2
2个点,那么
d
i
+
1
d_i+1
di+1就不可能超过
2
d
i
2d_i
2di。
而第二个不等式可以这样想,由于第
i
i
i层会向第
i
+
1
i+1
i+1层中选择不超过两个点,而两个不同的点如果选择的点一样,那么这两个点显然可以在第
i
i
i层合起来。
总共不同的选择方案有
(
d
i
+
1
+
1
)
2
−
1
(d_i+1+1)^2-1
(di+1+1)2−1种,这也就是
d
i
d_i
di的上限。
显然,我们最后一层
d
n
d_n
dn肯定只有一个点,因为它肯定是终止节点呢,不会再往后延伸,不可能有两个点。
那么,我们就可以从正反两个方向求出每个点的上限。
显然,对于这个上限,我们是很容易构造出一种方案达到的。
因此,我们将所有的上限加起来,就是我们第一问的答案。
知道了我们第一问的构造方法,就很容易想出我们的第二三问该怎么求了。
我们可以把整个分层图看成两个部分,
d
i
d_i
di取到第一个式子上限与
d
i
d_i
di取到第二个式子上限的部分。
显然,第一个部分到每个点所需要的数只有
1
1
1的,因为它们都只有
0
/
1
0/1
0/1的边,没有
0
,
1
0,1
0,1的边。
而第二个部分,到每个点所需要的数为该路径上
0
,
1
0,1
0,1的边数量的幂。显然,出现了这样的一条边就意味着这一位上既可以为
0
0
0也可以为
1
1
1,当然有两条边。
之后,我们要做的是将两边的点匹配。
一个左边的点可以匹配右边
1
/
2
1/2
1/2个点,有顺序,每个右边的至少被匹配到一次。
显然,如果我们要值最小的匹配,显然会尽量选择较小的对嘛。
较小的肯定就是先将所有点只连一条边的匹配选了,保证每个点都有匹配,再将剩余的没有选的对从小到大选。
较大的也可以用类似的方法,我们将右侧所有点与最大的一个点先与左边连了后,再从大到下选。
但我们发现这还有一个问题,如果我们分界处的
d
x
⩽
d
y
d_x\\leqslant d_y
dx⩽dy的情况,也就是我们最开始的连法都不能保证全部匹配的,对于这种情况我们就不得不妥协一下。
但我们可以发现,我们的
d
x
=
2
x
,
d
y
=
2
2
y
−
1
d_x=2^x,d_y=2^2^y-1
dx=2x,dy=22y−1。
那么显然,如果我们的
d
x
<
d
x
+
1
<
2
d
x
d_x<d_x+1<2d_x
dx<dx+1<2dx,显然,现在有
2
x
−
1
=
y
2x-1=y
2x−1=y。
也就是说,我们有一个点会多匹配一次,最小时就选最小值多匹配一次,最大时就选最大值多匹配一次。
左边的值,直接暴力翻倍就行了。
求出右侧的最大值我们可以采用
d
p
dp
dp的方式,事实上不同的数的个数各样是非常少的。
毕竟右边本来就是指数级递增,层数就相当少,又有许多重复的,最后得到
d
p
dp
dp状态是比较少的,完全可以
d
p
dp
dp,需要维护一个值的大小与方案数。
最后按值排序后就可以选择了。
注意,由于我们的
n
⩽
1000
n\\leqslant 1000
n⩽1000,答案是极大的,需要打高精。
但是实际上也不是特别大,暴力也跑的完,没必要打
F
F
T
FFT
FFT高精乘。
时间复杂度
O
(
∣
高
精
∣
∣
D
P
∣
)
O\\left(|高精||DP|\\right)
O(∣高精∣∣DP∣)。算不来…
事实上有不用dp,组合数统计的做法,跑得比较快,
D
D
(
X
Y
X
)
\\rm D\\redD(XYX)
DD(XYX)。反正不卡常,都能过。
源码
#include<bits/stdc++.h>
using namespace std;
#define MAXN 20005
typedef long long LL;
typedef pair<int,int> pii;
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
const int lim=10000000;
const int jzm=2333;
const int mo=998244353;
template<typename _T>
void read(_T &x)
_T f=1;x=0;char s=getchar();
while('0'>s||s>'9')if(s=='-')f=-1;s=getchar();
while('0'<=s&&s<='9')x=(x<<3)+(x<<1)+(s^48);s=getchar();
x*=f;
template<typename _T>
_T Fabs(_T x)return x<0?-x:x;
int add(int x,int y,int p)return x+y<p?x+y:x+y-p;
void Add(int &x,int y,int p)x=add(x,y,p);
int qkpow(int a,int s,int p)int t=1;while(s)if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;return t;
struct BigInt
int len;vector<LL>arr;BigInt()len=0;arr.clear();
BigInt(LL x)len=0;while(x)arr.pb(x%lim),len++,x/=lim;
void clear()len=0;arr.clear();
LL& operator [] (int x)return arr[x];
BigInt operator + (const LL &rhs)const
BigInt res;LL x=rhs;res.len=len;
res.arr.clear();res.arr.resize(len);
for(int i=0;i<len;i++)res[i]=arr[i]+x,x=res[i]/lim,res[i]%=lim;
while(x)res.arr.pb(x%lim),x/=lim,res.len++;
return res;
BigInt operator + (const BigInt &rhs)const
BigInt res;LL x=0;res.len=max(len,rhs.len);
res.arr.clear();res.arr.resize(res.len);int i;
for(i=0;i<len&&i<rhs.len;i++)
res[i]=arr[i]+rhs.arr[i]+x,x=res[i]/lim,res[i]%=lim;
while(i<len)res[i]=arr[i]+x,x=res[i]/lim,res[i]%=lim,i++;
while(i<rhs.len)res[i]=rhs.arr[i]+x,x=res[i]/lim,res[i]%=lim,i++;
while(x)res.arr.pb(x%lim),res.len++,x/=lim;
return res;
BigInt operator - (const BigInt &rhs)const
BigInt res;res.len=len;LL x=0;
res.arr.以上是关于[硫化铂]Minimal DFA的主要内容,如果未能解决你的问题,请参考以下文章