题解 G. Grammar Path 2020-2021 ICPC NERC (NEERC)

传送门


【大意】

给定一个 CNF 和一个有向图。有向图上的每一条边都写上了一个字母。

要求你从 \(s\)\(t\) 走一条尽可能短的路,且将经过的字母写下来后,这个字符串能被 CNF 接受。

输出字符串的串长。


【分析】

在有向图上行走,并写下边上的字符,这个过程等价于一个 DFA:

将所有状态的所有没提到过的其他字符全部指向一个状态,然后 \(s\) 为起始状态,\(t\) 为接受状态集里的唯一状态。

于是问题变化成了,给定一个 CNF 的 CFG 和一个 DFA ,求两者交集的 CFG 生成的最短串的串长。

关于生成该交集的 CFG 的方法,在本人的 上一篇博客 中有比较详细的说明。这个部分可以在 \(O(n^3p)\) 的时间内解决。

现在问题化为,给定一个 CFG ,怎么求它派生出的最短串长。


对于这个新生成的 CFG ,我们只要稍加转化便可以将它转化成 CNF 的形式。

对于这个 CNF,我们很难正面求解它派生的最短串串长,但可以通过逆推的方法求出:

我们对于每个变元,维护 \(minlen\) 表示这个变元派生的所有串中,最短的长度。这个过程显然是可以贪心的。

对于所有产生式 \(A\to c, c\in \Sigma\) ,很显然变元 \(A\) 的最短长度就是 \(1\)

其次,对于某个产生式规则 \(A\to BC\) ,若 \(B\)\(C\) 的最短长度均已知,可以确定 \(A\) 的最短长度一定不超过 \(minlen_B+minlen_C\) 。是否具有更短的要看其他的产生式规则。

为此,我们可以通过优先队列,参考堆优化 Dijkstra 算法的思想:

堆中维护某个变元和这个变元当前的最短长度,若弹出后发现已经被更新了,就跳过;

否则,预处理出每个变元出现在哪些产生式规则的右侧,然后更新这个产生式规则的最短长度。

若该产生式规则已经被更新了两次,则左侧的变元获得了一个最短长度的上界。松弛该变元的最短长度,并压入堆中。

最终我们只需要查询起始变元的最短长度是否被更新过即可。

由于每个变元最多的入堆次数和出现在产生式规则左侧的次数一致,因此堆内的元素数量和产生式规则的数量一致,为 \(O(n^3p)\)

而遍历每个变元出现在哪些产生式规则的右侧,以及松弛操作的次数,复杂度和产生式规则的长度和是一致的,在 CNF 中,又和产生式规则数量同阶,故为 \(O(n^3p)\)

因此这一步的总复杂度为 \(O(n^3p \log n^3p)\)

当然,需要注意的是,这题的变元数量达到了 \(O(n^2p)\) ,因此答案最大的上界可能达到 \(O(2^{n^2p})\) ,需要用到高精度。


【代码】

#include <bits/stdc++.h>
using namespace std;
const int MAXN=2e6+10;

struct bigint : public vector<int> {
	inline int cmp(const bigint &x) const {
		if(size()!=x.size()) {
			if(size()<x.size()) return -1;
			else return 1;
		}
		for(int i=size()-1; ~i; --i)
			if(at(i)!=x[i]) {
				if(at(i)<x[i]) return -1;
				else return 1;
			}
		return 0;
	}
	inline bigint& operator = (int num) {
		resize(0);
		for(; num; num/=10) push_back(num%10);
		reverse(begin(), end());
		return *this;
	}
	inline bool iszero() const { return size()==0; }
	inline friend ostream& operator << (ostream& out, const bigint &a) {
		if(a.iszero()) return out<<'0';
		for(int i=a.size()-1; ~i; --i) out<<a[i];
		return out;
	}
	inline friend bigint operator + (const bigint &a, const bigint &b) {
		bigint c;
		c.resize(max(a.size(), b.size()));
		for(int i=a.size()-1; ~i; --i) c[i]+=a[i];
		for(int i=b.size()-1; ~i; --i) c[i]+=b[i];
		for(int i=1; i<c.size(); ++i) c[i]+=c[i-1]/10, c[i-1]%=10;
		while(c.back()>=10) {
			int nxt=c.back()/10;
			c.back()%=10;
			c.push_back(nxt);
		}
		return c;
	}
};

struct node {
	int id;
	bigint num;
	inline node(int id_, bigint num_):id(id_), num(num_) {}
	inline friend bool operator < (const node &a, const node &b) {
		int cmp = a.num.cmp(b.num);
		if(cmp != 0) return cmp>0;
		return a.id < b.id;
	}
};

int p;
struct production {
	int from;
	int p1, p2;
	inline production() { from=p1=p2=-1; }
}ori[128], newp[MAXN];
int n, m, s, t, ch[26][26], cntp;

bigint minlen[70000], tmplen[MAXN];
vector<int> topro[70000];
priority_queue<node> pq;
inline bigint& ans() {	
	for(int i=1; i<=p; ++i)
		if(ori[i].p2>=0) {//A->BC
			for(int x=0; x<n; ++x)
				for(int y=0; y<n; ++y)
					for(int z=0; z<n; ++z) {
						production &p=newp[++cntp];
						p.from=ori[i].from*26*26+x*26+y;
						p.p1=ori[i].p1*26*26+x*26+z;
						p.p2=ori[i].p2*26*26+z*26+y;
						topro[p.p1].push_back(cntp);
						topro[p.p2].push_back(cntp);
					}
		}
		else{//A->a
			for(int x=0; x<n; ++x)
				for(int y=0; y<n; ++y)
					if(ori[i].p1==ch[x][y]) {
						production &p=newp[++cntp];
						p.from=ori[i].from*26*26+x*26+y;
						p.p1=ch[x][y];
						minlen[p.from]=1;
						pq.emplace(p.from, minlen[p.from]);
					}
		}
	
	while(!pq.empty()) {
		node now=pq.top(); pq.pop();
		if(now.num.cmp(minlen[now.id])>0)
			continue;
		for(auto e : topro[now.id]) {
			if(tmplen[e].iszero()) {
				tmplen[e]=minlen[now.id];
				continue;
			}
			tmplen[e]=tmplen[e]+minlen[now.id];
			int reduced = newp[e].from;
			if(minlen[reduced].iszero() || minlen[reduced].cmp(tmplen[e])>0) {
				minlen[reduced]=tmplen[e];
				pq.emplace(reduced, minlen[reduced]);
			}
		}
	}
	return minlen[18*26*26+s*26+t];
}
inline void init() {
	cin>>p;
	auto read_alpha = []() {
		char c=0;
		while(!isalpha(c)) cin>>c;
		return c;
	};
	for(int i=1; i<=p; ++i) {
		ori[i].from = read_alpha()-'A';
		char c=read_alpha();
		if(islower(c)) ori[i].p1 = c-'a';
		else ori[i].p1 = c-'A', ori[i].p2 = read_alpha()-'A';
	}
	
	cin>>n>>m>>s>>t;
	--s; --t;
	memset(ch, -1, sizeof(ch));
	for(int i=1, u, v; i<=m; ++i) {
		cin>>u>>v;
		ch[u-1][v-1] = read_alpha() - 'a';
	}
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	init();
	bigint &res=ans();
	if(res.iszero()) cout<<"NO";
	else cout<<res;
	return 0;
}
posted @ 2023-02-04 13:54  JustinRochester  阅读(60)  评论(0编辑  收藏  举报