「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)\)

小结:

  1. 适当转化模型。等价关系通常可以转化为无向图,而偏序关系通常可以转化为有向图,某些指定关系也可以转化为有向图,例如 2-SAT;
  2. 记录一下提到的经典转化,并且,在概率计算中这种忽略取等情况的转化也很常见;
  3. 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;
}
posted @ 2021-10-20 21:21  crashed  阅读(77)  评论(0编辑  收藏  举报