【LuoguP7155】【USACO20DEC】Spaceship P(矩阵乘法,动态规划)

题面

🔗

1 s    256 m b 1s~~256mb 1s  256mb

奶牛 Bessie 外星人绑架了,现在被关在了一艘外星人的飞船里!飞船有 N N N 1 ≤ N ≤ 60 1≤N≤60 1N60)间房间,编号为 1 … N 1…N 1N,其中某些房间之间由单向通过的门所连接(由于使用了奇怪的外星技术,一扇门甚至可能从一间房间通回这间房间本身!)。然而,没有两扇门具有完全相同的出发和到达房间。此外,Bessie 有一个遥控器,上有编号为 1 … K 1…K 1K 1 ≤ K ≤ 60 1≤K≤60 1K60)的按钮。

如果 Bessie 能够完成一个怪异的任务,外星人就会释放她。首先,他们会选择两间房间 s s s t t t 1 ≤ s , t ≤ N 1≤s,t≤N 1s,tN),以及两个整数 b s b_s bs b t b_t bt 1 ≤ b s , b t ≤ K 1≤b_s,b_t≤K 1bs,btK)。他们会将 Bessie 放在房间 s s s 内,并令她立刻按下按钮 b s b_s bs。然后 Bessie 需要继续在飞船内穿梭,同时按下按钮。有一些 Bessie 的行动需要遵守的规则:

  • 在每间房间内,在按下恰好一个按钮后,她必须选择从某扇门离开去往另一间房间(可能会回到同一间房间)或停止行动。
  • 一旦 Bessie 按下某个按钮,她再次按下这个按钮即为非法,除非在此之间她按下过编号更大的按钮。换句话说,按下编号为 x 的按钮会使得这个按钮变为非法,同时所有编号 < x <x <x 的按钮会被重置为合法。
  • 如果 Bessie 按下非法的按钮,任务即失败,外星人就会把她关起来。

仅当 Bessie 停止行动时位于房间 t t t,她最后按下的按钮是 b t b_t bt,并且没有按下过非法按钮时,Bessie 才会被释放。

Bessie 担心她可能无法完成这一任务。对于 Q Q Q 1 ≤ Q ≤ 60 1≤Q≤60 1Q60)个询问,每个询问包含一组 Bessie 认为可能的 s , t , b s s,t,b_s s,t,bs 以及 b t b_t bt,Bessie 想要知道可以使她得到释放的通过房间与按键序列的数量。由于答案可能非常大,输出对 1 0 9 + 7 10^9+7 109+7 取模的结果。

输入格式

输入的第一行包含 N , K , Q N,K,Q N,K,Q

以下 N N N 行每行包含 N N N 个二进制位( 0 0 0 1 1 1)。如果从房间 i i i 到房间 j j j 存在一扇门,则第 i i i 行的第 j j j 位为 1,如果没有这样的门则为 0。

以下 Q Q Q 行,每行包含四个整数 b s b_s bs s s s b t b_t bt t t t,分别表示起始按钮、起始房间、结束按钮、结束房间。

输出格式

Q Q Q 个询问的每一个,在一行内输出操作序列的数量模 1 0 9 + 7 10^9+7 109+7 的结果。

样例 #1

样例输入 #1

6 3 8
010000
001000
000100
000010
000000
000001
1 1 1 1
3 3 1 1
1 1 3 3
1 1 1 5
2 1 1 5
1 1 2 5
3 1 3 5
2 6 2 6

样例输出 #1

1
0
1
3
2
2
0
5

样例 #2

样例输入 #2

6 4 6
001100
001110
101101
010111
110111
000111
3 2 4 3
3 1 4 4
3 4 4 1
3 3 4 3
3 6 4 3
3 1 4 2

样例输出 #2

26
49
29
27
18
22

样例 #3

样例输入 #3

6 10 5
110101
011001
001111
101111
111010
000001
2 5 2 5
6 1 5 2
3 4 8 3
9 3 3 5
5 1 3 4

样例输出 #3

713313311
716721076
782223918
335511486
539247783

提示

门连接了房间 1 → 2 1→2 12 2 → 3 2→3 23 3 → 4 3→4 34 4 → 5 4→5 45 以及 6 → 6 6→6 66

对于第一个询问,Bessie 必须在按下第一个按钮后立刻停止行动。

对于第二个询问,答案显然为零,因为无法从房间 3 前往房间 1。

对于第三个询问,Bessie 的唯一选择是从房间 1 移动到房间 2 到房间 3,同时按下按钮 1、2 和 3。

对于第四个询问,Bessie 的移动方式是唯一的,她有三种可能的按键序列:

  • (1,2,3,2,1)
  • (1,2,1,3,1)
  • (1,3,1,2,1)

对于最后一个询问,Bessie 有五种可能的按键序列:

  • (2)
  • (2,3,2)
  • (2,3,1,2)
  • (2,1,3,2)
  • (2,1,3,1,2)

测试点性质:

  • 测试点 4-7 中, K ≤ 5 K≤5 K5 ( b s , s ) (b_s,s) (bs,s) 在所有询问中均相同。
  • 测试点 8-11 中,对所有询问有 b s = K − 1 b_s=K−1 bs=K1 以及 b t = K b_t=K bt=K
  • 测试点 12-15 中, N , K , Q ≤ 20 N,K,Q≤20 N,K,Q20
  • 测试点 16-23 没有额外限制。

题解

我们知道,图的邻接矩阵 G G G 可以当作走一步的转移矩阵,所以,对于一个长为 k k k 的合法按钮序列,它最终 N × N N\times N N×N 的答案矩阵即为 G k − 1 G^{k-1} Gk1

因此,我们可以用 D P DP DP 求出合法的按钮序列的答案矩阵和。

首先,我们考察合法的按钮序列的条件是什么:如果某个数前面第一个大于等于它的数与之相等,即相等的两个数之间没有比它们大的数,那么序列不合法。

这等价于该序列对应唯一的笛卡尔树。该序列的任意子段最大值都是唯一的。

接下来让我们想想需要计算些什么。

d p [ i ] dp[i] dp[i] 表示以数字 i i i 为开头,以 i + 1 i+1 i+1 为结尾( i + 1 i+1 i+1 可替换为任意大于 i i i 的数),中间的数都小于 i i i 的合法按钮序列矩阵和 d p [ 1 ] = G dp[1]=G dp[1]=G ,此外,枚举中间的最大值有如下转移:
d p [ i ] = G + ∑ j < i d p [ j ] 2 dp[i]=G+\sum_{j<i} dp[j]^2 dp[i]=G+j<idp[j]2

这部分预处理精细化实现可以达到 O ( K N 3 ) O(KN^3) O(KN3) d p [ ] dp[] dp[] 在后面会非常有用。


然后,对于一个询问,限制了序列开头 b s b_s bs 和结尾 b t b_t bt ,从头到另一头计算复杂度可能过高。我们其实可以枚举序列最大值,然后在中间碰头。确定序列最大值后,相当于把序列从中间割开了,两边可以分别考虑。

d p l [ i ] dpl[i] dpl[i] 表示以 b s b_s bs 开头, i i i 结尾,且 i i i 为最大值的序列答案矩阵和,那么 d p l [ b s ] = [ 1 ] dpl[b_s]=[1] dpl[bs]=[1] ,有转移:
d p l [ i ] = ∑ j < i d p l [ j ] ⋅ d p [ j ] dpl[i]=\sum_{j<i} dpl[j]\cdot dp[j] dpl[i]=j<idpl[j]dp[j]

类似地,定义 d p r [ i ] dpr[i] dpr[i] 表示以 b t b_t bt 结尾, i i i 开头,且 i i i 为最大值的序列答案矩阵和。 d p r [ b t ] = [ 1 ] dpr[b_t]=[1] dpr[bt]=[1] ,有转移:
d p r [ i ] = ∑ j < i d p [ j ] ⋅ d p r [ j ] dpr[i]=\sum_{j<i}dp[j]\cdot dpr[j] dpr[i]=j<idp[j]dpr[j]

不妨设 b s ≤ b t b_s\leq b_t bsbt ,答案矩阵 A A A 只需要左右相乘再求和:
A = d p l [ b t ] + ∑ j = b t + 1 K d p l [ j ] ⋅ d p r [ j ] A=dpl[b_t]+\sum_{j=b_t+1}^{K} dpl[j]\cdot dpr[j] A=dpl[bt]+j=bt+1Kdpl[j]dpr[j]

于是,我们每次询问都计算一遍,最后输出答案矩阵的 ( s , t ) (s,t) (s,t) 元素,时间复杂度 O ( Q K N 3 ) O(QKN^3) O(QKN3) ,可以过掉除了最后一档的所有分。

我们发现上述算法是极其浪费的,因为我们最终只需要 ( s , t ) (s,t) (s,t) 这一个元素。所以,其实可以把矩阵向量化,令 L L L 等于第 s s s 位为 1 的行向量 R R R 等于第 t t t 位为 1 的列向量,那么我们需要的就是
L ⋅ A ⋅ R = ( L ⋅ d p l [ b t ] ) ⋅ R + ∑ j = b t + 1 K ( L ⋅ d p l [ j ] ) ⋅ ( d p r [ j ] ⋅ R ) L\cdot A\cdot R=(L\cdot dpl[b_t])\cdot R+\sum_{j=b_t+1}^K(L\cdot dpl[j])\cdot (dpr[j]\cdot R) LAR=(Ldpl[bt])R+j=bt+1K(Ldpl[j])(dpr[j]R)

d p l dpl dpl d p r dpr dpr 都乘上向量:
L ⋅ d p l [ i ] = ∑ j < i ( L ⋅ d p l [ j ] ) ⋅ d p [ j ] d p r [ i ] ⋅ R = ∑ j < i d p [ j ] ⋅ ( d p r [ j ] ⋅ R ) L\cdot dpl[i]=\sum_{j<i} (L\cdot dpl[j])\cdot dp[j]\\ dpr[i]\cdot R=\sum_{j<i}dp[j]\cdot (dpr[j]\cdot R) Ldpl[i]=j<i(Ldpl[j])dp[j]dpr[i]R=j<idp[j](dpr[j]R)

这样乘法的复杂度就是 O ( N 2 ) O(N^2) O(N2) 了。

总时间复杂度 O ( K N 3 + Q K N 2 ) O(KN^3+QKN^2) O(KN3+QKN2)

CODE

#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<random>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
#define MAXN 105
#define LL long long
#define ULL unsigned long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define PR pair<int,int>
#define UIN unsigned int
int xchar() {
	static const int maxn = 1000000;
	static char b[maxn];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
	if(pos == len) return -1;
	return b[pos ++];
}
// #define getchar() xchar()
inline LL read() {
	LL f = 1,x = 0;int s = getchar();
	while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
	while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
	return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
inline void putnum(LL x) {
	if(!x) {putchar('0');return ;}
	if(x<0) putchar('-'),x = -x;
	return putpos(x);
}
inline void AIput(LL x,int c) {putnum(x);putchar(c);}

const int MOD = 1000000007;
int n,m,s,o,k;
void MD(int &x) {if(x>=MOD)x-=MOD;}
struct mat{
	int s[60][60],n,m;
	mat(){memset(s,0,sizeof(s));n=m=0;}
}G,dp0[65],pw0[65],dpl[65],dpr[65];
inline mat operator * (mat a,mat b) {
	mat c; c.n = a.n; c.m = b.m;
	for(int i = 0;i < a.n;i ++) {
		for(int k = 0;k < a.m;k ++) {
			if(a.s[i][k])
			for(int j = 0;j < b.m;j ++) {
				c.s[i][j] = (c.s[i][j] + a.s[i][k]*1ll*b.s[k][j]) % MOD;
			}
		}
	} return c;
}
inline mat operator + (mat a,mat b) {
	for(int i = 0;i < a.n;i ++) {
		for(int j = 0;j < a.m;j ++) {
			MD(a.s[i][j] += b.s[i][j]);
		}
	} return a;
}
inline mat& operator += (mat &a,mat b) {
	for(int i = 0;i < a.n;i ++) {
		for(int j = 0;j < a.m;j ++) {
			MD(a.s[i][j] += b.s[i][j]);
		}
	} return a;
}
int main() {
	n = read(); k = read(); m = read();
	G.n = G.m = n;
	for(int i = 0;i <= k;i ++) dp0[i].n = dp0[i].m = n;
	for(int i = 1;i <= n;i ++) {
		for(int j = 1;j <= n;j ++) {
			char c = getchar();
			while(c == ' ' || c == '\n') c = getchar();
			G.s[i-1][j-1] = c^48;
		}
	}
	mat mt0 = mat();
	mt0.n = mt0.m = n;
	mat mt1 = mt0,ml0 = mat(),mr0 = mat();
	ml0.n = 1; ml0.m = n;
	mr0.n = n; mr0.m = 1;
	for(int i = 0;i < n;i ++) mt1.s[i][i] = 1;
	dp0[1] = G; pw0[1] = dp0[1] * dp0[1];
	for(int i = 2;i <= k;i ++) {
		dp0[i] = pw0[i-1] + G;
		pw0[i] = dp0[i] * dp0[i];
		pw0[i] += pw0[i-1];
	}
	while(m --) {
		s = read(); int S = read();
		o = read(); int T = read();
		if(s > o) swap(s,o);
		for(int i = 1;i <= k;i ++) dpl[i] = ml0,dpr[i] = mr0;
		dpl[s].s[0][S-1] = 1; dpr[o].s[T-1][0] = 1;
		for(int i = s;i <= k;i ++) {
			mat nm = dpl[i] * dp0[i];
			for(int j = i+1;j <= k;j ++) dpl[j] += nm;
		}
		for(int i = o;i <= k;i ++) {
			mat nm = dp0[i] * dpr[i];
			for(int j = i+1;j <= k;j ++) dpr[j] += nm;
		}
		mat as = dpl[o] * dpr[o];
		for(int i = o+1;i <= k;i ++) as += dpl[i] * dpr[i];
		AIput(as.s[0][0],'\n');
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值