「Gym101612F」Fygon 2.0
题目
点这里看 F 题。
分析
很不错的一道题。
我们可以尝试改写一下循环语句:
for i in range(l, r):
其实等价于:
for i in range(1, n):
if( l <= i and i <= r ):
为了方便,这里“引入”了
if
语句,同时使用了python
的语法。当然,如果要扣细节的话,这里的
range
应该写成range(1,n+1)
😅。
也就是说,一个循环语句本质上对应了两组不等关系。
自然,我们可以想到通过不等关系构建有向图。如果有 \(x\le y\),那么就从 \(y\) 向 \(x\) 引一条有向边。
考察一下这个图。容易发现,如果 \(x\) 与 \(y\) 是强连通的,那么就有 \(x\le y\) 和 \(y\le x\),也即是 \(x=y\)。因此,我们可以知道,一个强连通分量内部,所有变量的值一定相同,那么就可以缩成一个点。现在有向图简化为了 DAG。
呃,现在问题还是比较复杂。比较容易想到的思路是,我们枚举变量的顺序,并且计算这样顺序下方案数的贡献。但由于关系为 \(\le\) 而非 \(<\),我们极其容易重复计算;想要解决这个问题可能不得不引入容斥或者更复杂的操作。
一个经典的转化可以帮忙:由于 \(n\rightarrow +\infty\),所以对于所有存在变量 \(x,y\) 满足 \(x=y\) 的方案,它们最终不会影响到极限。这是因为,如果 \(x=y\),则相当于合并了两个点,这一定会导致方案数中 \(n\) 的指数的减少,最终贡献是无穷小量。
这样,我们的有向边表示的就是“不取等”的不等关系 \(<\),于是我们就可以较容易地考虑变量之间的相对关系了。
假设变量为 \(x_1,x_2,\dots,x_k\),最终的不等关系用排列 \(p\in Sym(\{1,2,\dots,k\})\) 表示,也即是 \(1\le x_{p_1}<x_{p_2}<\dots<x_{p_k}\le n\)。容易计算出这对于极限的贡献为 \(\frac{\binom{n}{k}}{n^{\underline{k}}}=\frac{1}{k!}\),是一个仅仅与 \(k\) 相关的值。因此,现在问题转化为了,求满足 DAG 限制的 \(p\) 的个数。
容易发现,原 DAG 的拓扑序与合法的 \(p\) 一一对应(实际操作中只需翻转一下拓扑序),因此对于 DAG 的拓扑序计数即可,这是一个经典的状压 DP 问题,复杂度为 \(O(k2^k)\)。
小结:
- 适当转化模型。等价关系通常可以转化为无向图,而偏序关系通常可以转化为有向图,某些指定关系也可以转化为有向图,例如 2-SAT;
- 记录一下提到的经典转化,并且,在概率计算中这种忽略取等情况的转化也很常见;
- DAG 拓扑序计数的方法。
代码
#include <bits/stdc++.h>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
#define Waste( x ) for( int wtf = 1 ; wtf <= (x) ; wtf ++ ) getchar()
#define FindWaste( x ) while( ( tmp = getchar() ) != x )
typedef long long LL;
const int MAXN = 30, MAXS = ( 1 << 20 ) + 5;
struct Edge
{
int to, nxt;
}Graph[MAXN];
std :: map<char, int> dict;
LL dp[MAXS];
int pre[MAXN];
int stk[MAXN], top;
bool inStk[MAXN];
int bel[MAXN], tot;
int DFN[MAXN], LOW[MAXN], ID;
int head[MAXN];
int M, N, cnt = 1;
LL Gcd( LL x, LL y ) { for( LL z ; y ; z = x, x = y, y = z % y ); return x; }
void AddEdge( const int from, const int to )
{
Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
head[from] = cnt;
}
void Tarjan( const int u )
{
DFN[u] = LOW[u] = ++ ID;
inStk[stk[++ top] = u] = true;
for( int i = head[u], v ; i ; i = Graph[i].nxt )
{
if( ! DFN[v = Graph[i].to] )
Tarjan( v ), LOW[u] = std :: min( LOW[u], LOW[v] );
else if( inStk[v] ) LOW[u] = std :: min( LOW[u], DFN[v] );
}
if( DFN[u] == LOW[u] )
{
int v;
do inStk[v = stk[top --]] = false, bel[v] = tot;
while( v ^ u );
tot ++;
}
}
int main()
{
freopen( "fygon20.in", "r", stdin );
freopen( "fygon20.out", "w", stdout );
scanf( "%d", &M );
rep( i, 1, M - 1 )
{
char tmp, nam, l, r;
FindWaste( 'f' ); Waste( 3 ); nam = getchar();
if( dict.find( nam ) == dict.end() ) dict[nam] = ++ N;
FindWaste( '(' ); l = getchar(); Waste( 2 ); r = getchar();
if( l != '1' ) AddEdge( dict[nam], dict[l] );
if( r != 'n' ) AddEdge( dict[r], dict[nam] );
}
rep( i, 1, N ) if( ! DFN[i] ) Tarjan( i );
rep( u, 1, N )
for( int j = head[u], v ; j ; j = Graph[j].nxt )
if( bel[v = Graph[j].to] ^ bel[u] )
pre[bel[v]] |= 1 << bel[u];
dp[0] = 1;
for( int S = 1 ; S < ( 1 << tot ) ; S ++ )
for( int i = 0 ; i < tot ; i ++ )
{
if( ! ( S >> i & 1 ) ) continue;
int T = S ^ ( 1 << i );
if( ( pre[i] & T ) == pre[i] )
dp[S] += dp[T];
}
LL fac = 1, ans = dp[( 1 << tot ) - 1];
rep( i, 1, tot ) fac *= i;
LL d = Gcd( fac, ans );
printf( "%d %lld/%lld", tot, ans / d, fac / d );
return 0;
}