[UR#12]密码锁

122 篇文章 0 订阅

题目

传送门 to UOJ

前言

“一想到我是个☂️🅱️,许多问题就迎刃而解了。” —— 嚣张的我

最近把手弄断了,明显精神萎靡 + + + 嗜睡。整个下午和晚上拿去做这道题,完全做不出;本来人就蠢,精神还没法集中。快要神经衰弱了。

我的思维能力已经退化到了前所未有的境地,这是真的。

思路

以为是状压方法。但是无论如何都难以优化时,想想是不是 状态定义太差 或者 根本就不是 d p \tt dp dp 。一般原因相同:计算答案的方法太劣

我本以为,就算独立计算贡献,至少要钦定 S S S 为单个 s c c \rm scc scc 。事实却是毫无情面的东西,它让 D d ( X y X ) \sf Dd(XyX) Dd(XyX) 傲视群雄,让 O n e I n D a r k \sf OneInDark OneInDark 永沉海底。

竞赛图 s c c \rm scc scc 计数的套路:等价于非空点集 S ⫅ V S\subseteqq V SV 使得有向边都是从 S S S ∁ V S \complement_{V}S VS S S S 数量。相当于 c u t \rm cut cut 的数量。证明很容易,因为每个 s c c \rm scc scc 只会属于 S S S T = ∁ V S T=\complement_V S T=VS 之一,而 S S S 必须是拓扑序较小的、 T T T 必须是拓扑序较大的。

于是本题答案就是
∑ S Direct ( S , ∁ V S ) \sum_{S}\text{Direct}(S,\complement_V S) SDirect(S,VS)

由于 m m m 很小,考虑 D i r e c t \rm Direct Direct 中包含哪些特殊边。就想到,特殊边构成的连通块独立。对于每个连通块可以 O ( 2 s i z e ) \mathcal O(2^{\rm size}) O(2size) 暴力计算,外层对 ∣ S ∣ |S| S 作背包,用 ∣ S ∣ × ( n − ∣ S ∣ ) |S|\times (n{-}|S|) S×(nS) 得到普通边的数量。所以复杂度 O ( 2 m n + n 2 ) \mathcal O(2^{m}n+n^2) O(2mn+n2),跟 d p \tt dp dp 近乎无关。

代码

#include <cstdio> // megalomaniac JZM yydJUNK!!!
#include <iostream> // Almighty XJX yyds!!!
#include <algorithm> // oracle: ZXY yydBUS!!!
#include <cstring> // DDG yyd Black Bridge!!!
#include <cctype> // decent XYX yydLONELY!!!
typedef long long llong;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
	for(; isdigit(c); c=getchar()) a = a*10+(c^48);
	return a*f;
}

const int MAXN = 39, MOD = 998244353;
uint64_t gra[MAXN]; int dp[MAXN];

const int inver = 595552581; // 5000^(-1)
inline llong qkpow(llong b, int q){
	long long a = 1;
	for(; q; q>>=1,b=b*b%MOD) if(q&1) a = a*b%MOD;
	return a;
}

const int inv2 = (MOD+1)>>1;
int a[MAXN], b[MAXN], w[MAXN][2], tmp[MAXN];
int main(){
	int n = readint(), m = readint();
	rep(i,1,m){ // special edges
		a[i] = readint(), b[i] = readint();
		gra[a[i]] |= 1ull<<b[i], gra[b[i]] |= 1ull<<a[i];
		w[i][0] = readint(), w[i][1] = 10000-w[i][0];
		rep(j,0,1) w[i][j] = int(llong(inver)*w[i][j]%MOD);
	}
	rep(i,1,n) rep(j,1,n)
		if(gra[j]>>i&1) gra[j] |= gra[i]; // floyed
	uint64_t vis = 0; dp[0] = 1;
	rep(i,1,n) if(!(vis>>i&1)){
		memset(tmp+1,0,n<<2); gra[i] |= 1ull<<i;
		for(uint64_t S=gra[i]; S; S=(S-1)&gra[i]){
			int nowv = 1; // so easy
			rep(j,1,m) // brutely check edges
				if((S>>a[j])&(~S>>b[j])&1)
					nowv = int(llong(nowv)*w[j][0]%MOD);
				else if((S>>b[j])&(~S>>a[j])&1)
					nowv = int(llong(nowv)*w[j][1]%MOD);
			int siz = __builtin_popcountll(S);
			tmp[siz] = (tmp[siz]+nowv)%MOD;
		}
		vis |= gra[i]; // mark
		drep(p,n-1,0) rep(q,1,n-p) dp[p+q] = int(
			(dp[p+q]+llong(dp[p])*tmp[q])%MOD);
	}
	int ans = 0; // what the f**k
	rep(i,1,n) ans = int((ans+dp[i]
		*qkpow(inv2,(n-i)*i))%MOD);
	ans = int(qkpow(10000,n*(n-1))*ans%MOD);
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值