[航海协会]无损加密

无损加密

题目描述

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

题解

首先,我们可以把求行列式的条件转化一下。
一种经典的行列式转化方式就是 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=jlki<jrk]的矩阵。
我们尝试把它转化成图。
我们最开始的出发点相当于是 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 rili+1s,所以我们可以知道每一列上的入度与出度都是 ⩽ 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;
}

谢谢!!!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值