[硫化铂]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+12didi(di+1+1)21
第一个不等式说明的是我们的第 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)21种,这也就是 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 dxdy的情况,也就是我们最开始的连法都不能保证全部匹配的,对于这种情况我们就不得不妥协一下。
但我们可以发现,我们的 d x = 2 x , d y = 2 2 y − 1 d_x=2^x,d_y=2^2^y-1 dx=2x,dy=22y1
那么显然,如果我们的 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 2x1=y
也就是说,我们有一个点会多匹配一次,最小时就选最小值多匹配一次,最大时就选最大值多匹配一次。

左边的值,直接暴力翻倍就行了。
求出右侧的最大值我们可以采用 d p dp dp的方式,事实上不同的数的个数各样是非常少的。
毕竟右边本来就是指数级递增,层数就相当少,又有许多重复的,最后得到 d p dp dp状态是比较少的,完全可以 d p dp dp,需要维护一个值的大小与方案数。
最后按值排序后就可以选择了。

注意,由于我们的 n ⩽ 1000 n\\leqslant 1000 n1000,答案是极大的,需要打高精。
但是实际上也不是特别大,暴力也跑的完,没必要打 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的主要内容,如果未能解决你的问题,请参考以下文章

[硫化铂]守序划分问题

[硫化铂]传染

[硫化铂]密码

[硫化铂]treecnt

[硫化铂]启程的日子

[硫化铂]卿且去