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