#21. DFA

首先,观察题目可知,该题不存在无解的情况,可以建一颗深度为n的满二叉树,以根为起点,收到0时向左走,收到1时向右走,再观察题目发现(1<<n+1)-1正好9192≈10000,无需证明,这就是对的。所以接下来只要把能够合并的点都合并起来,就能得到符合题意的自动机。

YYMHL(%A题大爷)说了,只要n^2枚举,两两合并,就可以A掉这题。时间复杂度O(kn^2),k为未知常数,听说很小。然后施大说正解就是这样的。顿时无语到不想鏼这题。

然后考试后的汪大就默默地想出了一个n(logn)^2的算法,怒艹标算!然后我就烧四五个小时把它膜了一遍,然后就没有然后了……

因此,蒟蒻决定重点分析这个算法。

先挂个代码,不然讲不清楚。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define rep(i,j,k) for(i=j;i<=k;++i)
#define per(i,j,k) for(i=j;i>=k;--i)
#define ll long long
#define ldb long double
#define pii pair<int,int>
#define mkp make_pair
#define X first
#define Y second
const int N=10005;
int n,nn,dep[N],ha[N],par[N],tot,u,vis[N],pos[N],lst[N],key[N];pii tmp[N];
struct TREE{int lc,rc;bool flg;}T[N];
void rebuild(int x){
	if(vis[x])return;vis[x]=1;
	pos[key[x]=++u]=x;if(T[x].flg)lst[++tot]=u;
	rebuild(T[x].lc=par[T[x].lc]),rebuild(T[x].rc=par[T[x].rc]);
}
int main(){
	int i,x,j,l;char str[14];
	scanf("%d",&n);nn=(1<<n+1)-1;
	rep(i,1,nn){
		scanf("%s",str);
		x=1;l=strlen(str)-1;
		rep(j,1,l)x=x<<1|str[j]=='1';
		if(x<<1<nn)T[x]=(TREE){x<<1,x<<1|1,str[0]=='+'};
		else T[x]=(TREE){x,x,str[0]=='+'};
	}
	rep(i,1,nn)dep[par[i]=i]=dep[i>>1]+1;
	for(l=nn,i=0;i<n;++i,l>>=1){
		rep(j,1,l)tmp[j]=mkp(ha[j]=ha[T[j].lc]+l*ha[T[j].rc]<<1|T[j].flg,j);
		sort(tmp+1,tmp+l+1);
		x=ha[tmp[1].Y]=0;
		rep(j,2,l)ha[tmp[j].Y]=x+=tmp[j].X>tmp[j-1].X;
		x=tmp[1].Y;
		rep(j,2,l)
			if(tmp[j].X==tmp[j-1].X){
				if(dep[tmp[j].Y]>n-i)par[tmp[j].Y]=x;
			}
			else x=tmp[j].Y;
	}
	rebuild(1);
	printf("%d 1\n%d",u,tot);
	rep(i,1,tot)printf(" %d",lst[i]);puts("");
	rep(i,1,u)printf("%d %d\n",key[T[pos[i]].lc],key[T[pos[i]].rc]);
	return 0;
}

另外附一张优先级的图(压行走火入魔了……

该图来自百度百科

T代表节点,lc、rc分别表示接收0、1后走到的节点,flg表示节点代表的串会被接受(1)还是拒绝(0)。

汪大说,叶子节点的lc、rc都要指向自己,这也许和rebuild有关。

给定每个点一个哈希值(用ha[]表示),其囊括了该节点本身与左右子树的flg信息,若两个节点的ha相同,则说明这两个节点完全相同,可以合并。哈希之后为了避免取模,可以进行离散化。

接着从大到小枚举深度,每次只能将枚举到深度的点与深度小于等于它的点合并。合并用到并查集。注意这里的并查集没有用到getpar(),原因是所有ha相同的节点都只会被连向编号最小的那个节点(即x,靠tmp维护)。

rebuild的过程比较水,只要从1开始搜,把走到的点压到一起(lst[]中,个数为u),其中flg为1的点压到一起(pos[]中,总数为tot)。注意得到的结果还不能直接输出,原因是要重新编号。我就是因为没想到这点,被simpleoj卡掉了80分。。。

应该差不多了。

算上考试和写博客,这一天全烧在这题上了。惨!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值