8.20T3 无损加密(线性代数转LGV+状压dp+高维前缀和)

http://cplusoj.com/d/senior/p/NODSX2301C

对于式子:

在这里插入图片描述

这个神秘的线性代数形式比较难处理,但我们可以考虑其组合意义。行列式现存的可用组合意义之一就是LGV(矩阵式不太可用)

先把原先的矩阵转化为一个有向图。现在我们要构造一个图,满足 B i , j B_{i,j} Bi,j 代表从 a i a_i ai 走到 b j b_j bj 所有路径权值积的和为 B i , j B_{i,j} Bi,j,而上面行列式求的就是从 a 1 → n a_{1\to n} a1n 走到 b b b 的任意 ( m n ) \binom m n (nm) 个点的不想交路劲数。

现在我们已经理清条件,考虑构造图了。我们先弄一行 a a a b b b

在这里插入图片描述

现在是满足条件的。

我们考虑在 [ l , r ] [l,r] [l,r] 内加入 d , c d,c d,c,不妨令其为 [ 2 , 3 ] [2,3] [2,3],在外面加没问题:

在这里插入图片描述

而对于 [l,r] 内的情况,我们相当于在原先基础上多了一个不断往右增 c c c 的边:

在这里插入图片描述

这样子建边能完美解决我们的问题。

现在只需要对最终的图统计不相交路径数即可。

我们考虑第 i i i 列的链:

在这里插入图片描述

根据题目,这条链的总入度和总出度不超过 s s s,其实我们状压一下。

我们可以先钦定哪些入度有流,从上一步的 f [ S ] f[S] f[S] 转移过来,当然必须满足红色点也有流。

然后我们直接让这些流先走到最近的出度。

当走到最近的出度后,他们还可以往下走,此时我们可以拿一个类似高维前缀和的东西来实现。而这里,我们要按顺序,先走最上面,再往下一个一个考虑,那样子才能保证路径不交。

我们可以设 g ( s , i ) g(s,i) g(s,i) 表示当前状态是 s s s,考虑到第 i i i 个1的路径权值积之和、

最后我们把黄色点的东西去掉,就可以转移到新 f [ T ] f[T] f[T]

答案为 f [ 0 ] f[0] f[0]

复杂度我实现有点劣,为 O ( n 2 s s 2 ) O(n2^s s^2) O(n2ss2),但很多情况会直接停止,所以跑不满。

4k

#include<bits/stdc++.h>
using namespace std;
#ifdef LOCAL
 #define debug(...) fprintf(stdout, ##__VA_ARGS__)
 #define debag(...) fprintf(stderr, ##__VA_ARGS__)
#else
 #define debug(...) void(0)
 #define debag(...) void(0)
#endif
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define Z(x) (x)*(x)
#define pb push_back
#define fi first
#define se second
#define M 9
#define mo (int)(1e9 + 7)
#define N 200010
int pw(int a, int b) {
	int ans = 1; 
	while(b) {
		if(b & 1) ans *= a; 
		a *= a; b >>= 1; 
		ans %= mo; a %= mo; 
	}
	return ans; 
}
int pw(int a) { return pw(a, mo - 2); }
inline void Mod(int &a) { if(a >= mo || a <= -mo) a %= mo; if(a < 0) a += mo; }
inline void Add(int &a, int b) { a += b; Mod(a); }
inline void Mul(int &a, int b) { Mod(b); a *= b; Mod(a); } 
int n, m, i, j, k, T;
int C[N * M], D[N * M], q, l, r, Ds[N * M]; 
vector<int>L[N], R[N]; 
int nl, nr, s, t, f[1 << 10], g[1 << 10][11], nxt[15]; 
int ans, c[15], d[15], del[15]; 

signed main()
{
	#ifdef LOCAL
	  freopen("in.txt", "r", stdin);
	  freopen("out.txt", "w", stdout);
	#endif
//	srand(time(NULL));
//	T=read();
//	while(T--) {
//
//	}
	n = read(); m = read(); q = read(); Ds[0] = C[q + 1] = 1; 
	for(T = 1; T <= q; ++T) {
		l = read(); r = read(); C[T] = read(); D[T] = read(); 
		Ds[T] = D[T] * Ds[T - 1] % mo; 
		for(j = l + 1; j <= r; ++j) L[j].pb(T); 
		for(j = l; j < r; ++j) R[j].pb(T); 
	}
	Ds[q + 1] = Ds[q]; 
	debug("Ds : "); for(i = 0; i <= q + 1; ++i) debug("%lld ", Ds[i]); debug("\n"); 
	f[0] = 1; 
	for(T = 1; T <= m; ++T) {
		nl = L[T].size(); j = 0; 
		R[T].pb(q + 1); nr = R[T].size(); 
		for(auto t : L[T]) debug("%lld ", t); debug("\n"); 
		for(auto t : R[T]) debug("%lld ", t); debug("\n"); 
		for(auto t : L[T]) {
			for(k = 0; k < nr; ++k) 
				if(R[T][k] >= t) break; 
			nxt[j] = k; del[j] = Ds[R[T][k]] * pw(Ds[t]) % mo; ++j; 
			debug("[%lld %lld]%lld ", t, R[T][k], nxt[j - 1]); 
		}
		debug("\n"); 
		memset(g, 0, sizeof(g)); 
		for(s = 0; s < (1 << nl); ++s) {
			debug("f[%lld] = %lld\n", s, f[s]); 
			t = (T <= n ? 1 : 0); 
			int G = (T <= n ? Ds[R[T][0]] : 1); 
			for(j = 0; j < nl; ++j) {
				if(!(s & (1 << j))) continue; 
				if(t & (1 << nxt[j])) break; 
				t |= (1 << nxt[j]); G = G * del[j] % mo; 
			}
			if(j < nl) continue; 
			debug("--> %lld[%lld %lld] += %lld\n", t, t, 1ll, f[s]); 
//			if(!t) Add(g[t][0], f[s]); 
//			else 
			Add(g[t][1], f[s] * G % mo); 
		}
		for(i = 0; i < nr; ++i) c[i] = C[R[T][i]]; 
		for(i = 0; i < nr - 1; ++i) d[i] = Ds[R[T][i + 1]] * pw(Ds[R[T][i]]) % mo; 
		debug("# C : "); for(i = 0; i < nr; ++i) debug("%lld ", c[i]); debug("\n"); 
		debug("# D : "); for(i = 0; i < nr - 1; ++i) debug("%lld ", d[i]); debug("\n"); 
		for(i = 1; i <= nl + 1; ++i) //第 
			for(s = 0; s < (1 << nr); ++s) {
//				debug("(%lld %lld)\n", s, i); 
				if(!g[s][i]) continue; 
				if(__builtin_popcount(s) < i) continue; 
				for(k = j = 0; k < nr; ++k) {
					if((s >> k) & 1) ++j; 
					if(j == i) break; 
				}
				debug("(%lld %lld)%lld * %lld [%lld] => (%lld %lld)\n", s, i, g[s][i], c[k], k, s, i + 1); 
				Add(g[s][i + 1], g[s][i] * c[k] % mo); 
				if(k + 1 >= nr || ((s >> k + 1) & 1)) continue; 
				t = s - (1 << k) + (1 << k + 1); 
				debug("(%lld %lld)%lld * %lld [%lld] => (%lld %lld)\n", s, i, g[s][i], d[k], k, t, i); 
				Add(g[t][i], g[s][i] * d[k] % mo); 
			}
		memset(f, 0, sizeof(f)); 
		for(s = 0; s < (1 << nr); ++s) {
			t = s & (1 << nr - 1) - 1; 
			int cnt = __builtin_popcount(s); 
			debug(">>> %lld ---> %lld (%lld %lld)[%lld]\n", s, t, s, cnt + 1, g[s][cnt + 1]); 
			Add(f[t], g[s][cnt + 1]); 
		}
		debug("------------\n"); 
	}
	ans = f[0]; 
	printf("%lld", ans); 
//	for(i = 1; i <= m; ++i) assert(L[i].size() <= 8 && R[i].size() <= 8); 
	return 0;
}



  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值