无损加密
题目描述
题解
首先,我们可以把求行列式的条件转化一下。
一种经典的行列式转化方式就是
L
G
V
LGV
LGV引理,将我们的行列式转化成不想交路径的方案数。
显然,我们每次的转移相当于乘上一个
a
i
,
j
=
[
i
=
j
∨
l
k
⩽
i
<
j
⩽
r
k
]
a_{i,j}=[i=j\vee l_k\leqslant i< j\leqslant r_k]
ai,j=[i=j∨lk⩽i<j⩽rk]的矩阵。
我们尝试把它转化成图。
我们最开始的出发点相当于是
1
,
2
,
3
,
.
.
.
,
n
1,2,3,...,n
1,2,3,...,n,我们的终点是
1
,
2
,
3
,
.
.
.
,
m
1,2,3,...,m
1,2,3,...,m,最开始是这个样子的,借用一下
c
r
a
s
h
e
d
\rm crashed
crashed的图:
原矩阵的乘
d
d
d相当于就是从这个点到下一个点的边权为
d
d
d。同样,横着也相当于每走一步就要乘
c
c
c。
所以我们最后建出的图大概是这个样子的:
我们考虑怎么计算这个的总方案数。
由于
r
i
−
l
i
+
1
⩽
s
r_i-l_i+1\leqslant s
ri−li+1⩽s,所以我们可以知道每一列上的入度与出度都是
⩽
s
\leqslant s
⩽s的。
我们可以考虑一列一列地状压转移。
首先我们的转移是要求点不交的,我们每次就相当对于当前在这一列上覆盖到的入点,转移到其对应的出点,时间复杂度是
O
(
m
2
2
s
)
O\left(m2^{2s}\right)
O(m22s)的。
由于点不交,其实我们可以预处出来当前状态的每个点转移到的目标点有哪些,这样按道理来说会快一些,不过还是过不了。
更加经典的方法是可以用类似高维前缀和的方法进行转移。
先把所有的入点都映射到它后面的第一个出点上,显然它是至少要走到这个点上的。如果有点映射到了同一个点上,那肯定是不行的了。
之后,我们再考虑在每一个出点上的点是否转移到接下来的一个出点上面,一条边一条边地进行转移。显然,只有当这个点与下一个点都有是才不能转移。
由于我们是从上往下转移的,所以上一个点的终末位置时不会超过下一个点的初始位置,所以它们是合法的。
我们当这一列都赚完了后,再从这个出点转移到下一个入点,这只需要把每个状态的编号换到下一个状态上就行了。
注意就是从这一列的最后一个点是可以从这一列的末尾走出去的。
总时间复杂度 O ( q 2 s ) O\left(q2^s\right) O(q2s)。
源码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
typedef unsigned int uint;
#define MAXN 200005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
const LL INF=0x3f3f3f3f3f3f3f3f;
const int mo=1e9+7;
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,m,Q,ans,now,las;
int degin[MAXN],degout[MAXN],dp[2][605];
int sta[30],stak,ip[30];
vector<int>vin[MAXN],vout[MAXN],vec[MAXN];
struct ming{
int l,r,c,d;ming(){l=r=c=d=0;}
ming(int L,int R,int C,int D){l=L;r=R;c=C;d=D;}
}s[MAXN*10];
struct tann{
int id,s,nw;tann(){id=s=nw=0;}
tann(int I,int S,int N){id=I;s=S;nw=N;}
};
queue<tann>q;
int main(){
freopen("encode.in","r",stdin);
freopen("encode.out","w",stdout);
read(n);read(m);read(Q);bool fg=0;int summ=1;
for(int i=1;i<=Q;i++){
int l,r,c,d;read(l);read(r);read(c);read(d);
s[i]=ming(l,r,c,d);fg|=(c>1||d>1);summ=1ll*d*summ%mo;
for(int j=l+1;j<=r;j++)vin[j].pb(i),degin[j]++;
for(int j=l;j<r;j++)vout[j].pb(i),degout[j]++;
}
now=0;las=1;dp[0][0]=1;
for(int i=1;i<=m;i++){
int szin=vin[i].size(),szot=vout[i].size();stak=0;
if(i<=n){
sta[stak++]=0;swap(now,las);
for(int j=0;j<(1<<degin[i]);j++)
dp[now][j<<1|1]=dp[las][j],dp[las][j]=0;
}
for(int j=0;j<szin;j++)sta[stak++]=vin[i][j];
for(int j=stak-1,k=szot;j>=0;j--){while(k>0&&vout[i][k-1]>=sta[j])k--;ip[j]=k;}
swap(now,las);
for(int S=0;S<(1<<stak);S++)if(dp[las][S]){
int nS=0,ls=-1;bool flag=0;
for(int j=0;j<stak;j++)if((S>>j)&1)
nS|=(1<<ip[j]),flag|=(ls==ip[j]),ls=ip[j];
if(!flag)Add(dp[now][nS],dp[las][S],mo);dp[las][S]=0;
}
stak=0;for(int j=0;j<szot;j++)sta[stak++]=vout[i][j];sta[stak++]=Q+1;
for(int j=0;j<stak-1;j++){
swap(now,las);int nS=(1<<j)|(1<<j+1);
for(int k=0;k<(1<<stak);k++)if(dp[las][k]){
int tp=dp[las][k];dp[las][k]=0;Add(dp[now][k],tp,mo);
if((k&nS)==(1<<j))Add(dp[now][k^nS],tp,mo);
}
}
swap(now,las);
for(int S=0;S<(1<<stak);S++)if(dp[las][S]){
int tmp=dp[las][S],T=0;dp[las][S]=0;
for(int k=0;k<stak-1;k++)if((S>>k)&1)
tmp=1ll*s[sta[k]].c*tmp%mo,T|=(1<<k);
Add(dp[now][T],tmp,mo);
}
}
printf("%lld\n",1ll*qkpow(summ,n,mo)*dp[now][0]%mo);
return 0;
}