zoukankan      html  css  js  c++  java
  • 「SOL」Spaceship(LOJ/USACO)

    USACO 好难啊


    # 题面

    (n) 个点之间有若干条有向边连接,保证边 (u o v) 不超过一条,但可能同时有 (u o v,v o u),也可以有自环。

    你有 (m) 个按钮编号为 (1sim m),最初所有按钮都「可用」。当你按下按钮 (x) 后,按钮 (x) 会被「禁用」(你不能按当前被禁用的按钮),同时按钮 (1sim x-1) 全部恢复成可用。

    给定 (Q) 组询问,每组询问给出 (s,t,b_s,b_t),表示:你从 (s) 点出发,开始时强制按钮 (b_s) 变为「禁用」;你需要按一个按钮,然后走一步;求有多少种方案,满足当你到达 (t) 点时,你上一次按的按钮为 (b_t)

    数据规模:(n,m,qle60)


    # 解析

    暂时不管 (b_s,b_t) 的限制。

    根据规则,我们按下按钮 (i) 后,小于 (i) 的按钮都会重置,于是比较自然地想到计算子问题「只用小于等于 (i) 的按钮」。

    定义朴素 DP 状态:(f(i,s,t)) 表示「只按小于等于 (i) 的按钮,从 (s) 跑到 t$ 有多少种方案」。记 (g(s,t)) 表示是否存在边 (s o t),存在为 (1),不存在为 (0)

    考虑 (f) 的转移 —— 「只按小于等于 (i) 的按钮」,意味着当我们按下 (i) 后,(i) 就永远禁用了,于是可以先分两类再拆三段:

    • 不按按钮 (i)(f(i,s,t)gets f(i-1,s,t))(“(gets)” 在这里表示 “贡献”);
    • 按按钮 (i),可以拆成三段:
      • 先在 (1sim i-1) 中瞎乱按:(f(i-1,s,x))
      • 按按钮 (i)(e(x,y))
      • 再在 (1sim i-1) 中瞎乱按:(f(i-1,y,t))

    [f(i,s,t)=f(i-1,s,t)+sum_{x,y}f(i-1,s,x)e(x,y)f(i-1,y,t) ]

    记矩阵 (E)(E_{ij}=e(i,j))(F^i)(F^i_{st}=f(i,s,t))

    矩阵编号写成下标太难受了 ::>_<::

    于是 DP 的转移方程就可以写成:

    [F^i=F^{i-1}+F^{i-1}EF^{i-1} ]

    可以 (mathcal{O}(n^4)) 处理出所有的 DP 值。

    再考虑 (b_s,b_t) 的限制。仍然分析「只按小于等于 (b_{mx}) 的按钮」这样的子问题,不过这次要求一定要按 (b_{mx})

    把方案从按下 (b_{mx}) 这个位置分成两半计算,因为 (b_{mx}) 既然是按的最大的按钮,肯定只能按一次。

    定义 DP 状态 (p(i,x)) 表示:(b_{mx}=i) 时,从 (s) 出发,按钮初始状态为“只有 (b_s) 禁用”,每走一步按一次按钮(这样方便理解一些),走到 (x) 时按下 (b_{mx}) 的方案数。

    根据定义,DP 初值为 (p(b_s,s)=1)。一种方案可以看成两部分:

    • 只在小于等于 (j) 的按钮中乱按,从 (s) 跑到 (y),且最后按下按钮 (j)(此时小于等于 (j-1) 的按钮重置);
    • 在小于等于 (j-1) 的按钮中乱按,从 (y) 跑到 (z)
    • 按下 (i),从 (z) 跑到 (x),结束。

    [p(i,x)=sum_{jlt i}sum_{y,z}p(j,y)f(j-1,y,z)e(z,x) ]

    同样,写成矩阵的形式,一个一行 (n) 列的矩阵。(P^i)(P^i_{1j}=p(i,j))

    [P^i=sum_{jlt i}P^jF^{j-1}E=left(sum_{jlt i}P^jF^{j-1} ight)E ]

    注意到 (P)(1 imes n) 的,上述转移可以 (mathcal{O}(n^3)) 计算出所有的 (p(i,x))

    再推导后一半:按下按钮 (b_{mx}) 最后按 (b_t)。记 (q(i,x)) 表示 (b_{mx}=i) 时,从 (x) 出发,按钮初始状态为“只有 (b_{mx}) 为禁用”,按一次按钮走一步,走到 (t) 且最后一次按 (b_t) 的方案数。

    答案则为

    [sum_{i=1}^msum_{x=1}^np(i,x)q(i,x) ]

    注意到 (q)(p) 在某种程度上是高度对称的,我们可以较为简单地得到 (q) 的转移 —— 基本上就是把整个过程倒过来想。

    • 最后在小于等于 (j-1) 的按钮中乱按,从 (z) 跑到了 (t)
    • 之前在小于等于 (j) 的按钮中乱按,最后一次按了按钮 (j),从 (y) 跑到了 (z)
    • 最开始按了按钮 (i),从 (x) 跑到了 (y)

    [q(i,x)=sum_{j<i}sum_{y,z}e(x,y)f(j-1,y,z)q(j,z) ]

    同理可以 (mathcal{O}(n^3)) 求解。

    所以总复杂度为 (mathcal{O}(n^4+Qn^3)),写成矩阵可以让代码略微好看一些……


    # 源代码

    /*Lucky_Glass*/
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    inline int rin(int &r){
    	int b=1,c=getchar();r=0;
    	while(c<'0' || '9'<c) b=c=='-'?-1:b,c=getchar();
    	while('0'<=c && c<='9') r=(r<<1)+(r<<3)+(c^'0'),c=getchar();
    	return r*=b;
    }
    const int N=65,MOD=1e9+7;
    #define con(type) const type &
    inline int add(con(int)a,con(int)b){return a+b>=MOD?a+b-MOD:a+b;}
    inline int sub(con(int)a,con(int)b){return a-b<0?a-b+MOD:a-b;}
    inline int mul(con(int)a,con(int)b){return int(1ll*a*b%MOD);}
    inline int ina_pow(con(int)a,con(int)b){return b?mul(ina_pow(mul(a,a),b>>1),(b&1)?a:1):1;}
    inline void upd(int &a,con(int)b){a=add(a,b);}
    
    int n,m,nq;
    char str[N];
    
    struct Matrix{
    	int mat[N][N];
    	Matrix(){memset(mat,0,sizeof mat);}
    	inline int* operator [](con(int)i){return mat[i];}
    	Matrix operator *(con(Matrix)b)const{
    		Matrix c;
    		for(int i=1;i<=n;i++)
    			for(int k=1;k<=n;k++)
    				for(int j=1;j<=n;j++)
    					upd(c.mat[i][j],mul(mat[i][k],b.mat[k][j]));
    		return c;
    	}
    	void operator +=(con(Matrix)b){
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=n;j++)
    				upd(mat[i][j],b.mat[i][j]);
    	}
    }m_tra,m_def[N];
    
    int vs[N][N],vt[N][N],tmp[N],tmp2[N];
    
    int main(){
    	// freopen("input.in","r",stdin);
    	rin(n),rin(m),rin(nq);
    	for(int u=1;u<=n;u++){
    		scanf("%s",str+1);
    		for(int v=1;v<=n;v++)
    			m_tra[u][v]=str[v]=='1';
    	}
    	for(int i=1;i<=n;i++)
    		m_def[0][i][i]=1;
    	for(int i=1;i<=m;i++){
    		m_def[i]=m_def[i-1];
    		m_def[i]+=m_def[i-1]*m_tra*m_def[i-1];
    	}
    	for(int mq=1,bs,bt,s,t;mq<=nq;mq++){
    		rin(bs),rin(s),rin(bt),rin(t);
    		memset(vs,0,sizeof vs);
    		memset(vt,0,sizeof vt);
    		// “走完这一步,按一次按钮” -> “最初在 s 点直接按一次按钮”
    		vs[bs][s]=1;
    		for(int i=1;i<=n;i++) tmp[i]=m_def[bs-1][s][i];
    		for(int i=bs+1;i<=m;i++){
    			for(int p=1;p<=n;p++)
    				for(int q=1;q<=n;q++)
    					upd(vs[i][q],mul(tmp[p],m_tra[p][q]));
    			for(int p=1;p<=n;p++)
    				for(int q=1;q<=n;q++)
    					upd(tmp2[q],mul(vs[i][p],m_def[i-1][p][q]));
    			for(int p=1;p<=n;p++) upd(tmp[p],tmp2[p]),tmp2[p]=0;
    		}
    		// “按一次按钮,走一步” -> “最后在 t 直接按一次”
    		vt[bt][t]=1;
    		for(int i=1;i<=n;i++) tmp[i]=m_def[bt-1][i][t];
    		for(int i=bt+1;i<=m;i++){
    			for(int p=1;p<=n;p++)
    				for(int q=1;q<=n;q++)
    					upd(vt[i][q],mul(tmp[p],m_tra[q][p]));
    			for(int p=1;p<=n;p++)
    				for(int q=1;q<=n;q++)
    					upd(tmp2[q],mul(vt[i][p],m_def[i-1][q][p]));
    			for(int p=1;p<=n;p++) upd(tmp[p],tmp2[p]),tmp2[p]=0;
    		}
    		int ans=0;
    		for(int i=1;i<=m;i++)
    			for(int j=1;j<=n;j++)
    				upd(ans,mul(vs[i][j],vt[i][j]));
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    THE END

    Thanks for reading!

    是灵台菩提的初萌
    是神游蝶茧的悸动
    是万物的方生方死
    鱼跃方成龙
    是弹指白驹的飞纵
    是刹那朝菌的临终
    待天地封冻 可曾怨西风

    ——《万象霜天》By 赤羽

    > Link 万象霜天-Bilibili

    欢迎转载٩(๑❛ᴗ❛๑)۶,请在转载文章末尾附上原博文网址~
  • 相关阅读:
    软件实现的施密特触发器
    激励
    正式搬家,到博客园
    IAR编译器的常见问题
    记正式开始工作
    调度器的介绍
    atmega8 例程:AD中断方式采集
    【IAR警告】Warning[Pa082]: undefined behavior: the order of volatile accesses is undefined
    AD转换器的参数介绍
    影响LIMIT子句使用的一个mysql配置项
  • 原文地址:https://www.cnblogs.com/LuckyGlass-blog/p/14497508.html
Copyright © 2011-2022 走看看