zoukankan      html  css  js  c++  java
  • Atcoder Grand Contest 013 E Placing Squares(组合意义转化+矩阵快速幂/代数推导,思维题)

    Atcoder 题面传送门 & 洛谷题面传送门

    这是一道难度 Cu 的 AGC E,碰到这种思维题我只能说:not for me,thx

    然鹅似乎 ycx 把题看错了?

    首先这个平方与乘法比较喜闻乐见,很容易与组合联系在一起,于是我们不妨把题目条件翻译成组合的语言:

    • 有一排 \(n\) 个格子,你要在其中插入若干个隔板将其隔成若干段
    • \(m\) 个特殊格子 \(a_1,a_2,\dots,a_m\)\(\forall i\in [1,m]\) 你禁止在 \(a_i\)\(a_{i}+1\) 之间放隔板
    • 在相邻隔板之间的格子中需恰好放上一个黑球和一个白球(可以重合)

    不难发现,上述题面中的”隔板“对应原题中的正方形边界,对于一段长度为 \(l\) 的段,在上面放一个黑球和一个白球的方案数为 \(l^2\),也就对应了原题面中的”平方“,而原题中的乘法在上述题面中被翻译成了乘法原理。因此我们就将这题与组合意义联系在了一起。

    考虑设 \(dp_{i,j}\) 表示现在考虑了前 \(i\) 个位置,在当前位置到上一个隔板的区间中放上了 \(j\) 个球的方案数,其中 \(j\in [0,2]\)

    显然对于非特殊格子 \(i\) 我们有状态转移方程:

    • \(dp_{i+1,0}=dp_{i,0}+dp_{i,2}\)(在 \(i\)\(i+1\) 之间放隔板或不放隔板)
    • \(dp_{i+1,1}=2dp_{i,0}+dp_{i,1}+2dp_{i,2}\)(如果放隔板,那么只能从 \(dp_{i,2}\) 转移过来,由于有黑白两种颜色的求所以乘个 \(2\);如果不放隔板,那么可以从 \(dp_{i,0},dp_{i,1}\) 转移过来,同理 \(dp_{i,0}\) 前的系数也需乘个 \(2\)
    • \(dp_{i+1,2}=dp_{i,0}+dp_{i,1}+2dp_{i,2}\)(如果放隔板,那么只能从 \(dp_{i,2}\) 转移过来;如果不放隔板,那么可以从 \(dp_{i,0},dp_{i,1},dp_{i,2}\) 转移过来,以上四种情况均只有一种放法,而 \(dp_{i,2}\) 前系数的 \(2\) 是因为 \(dp_{i,2}\) 出现了两次)

    对于特殊格子 \(i\) 我们同样可以得到类似的状态转移方程:

    • \(dp_{i+1,0}=dp_{i,0}\)
    • \(dp_{i+1,1}=2dp_{i,0}+dp_{i,1}\)
    • \(dp_{i+1,2}=dp_{i,0}+dp_{i,1}+dp_{i,2}\)

    那么这样转化有什么好处呢?在原题中我们直接 \(dp\) 不是太容易,或者说我们只能想出 1D1D 的 \(dp\) 状态。而在转化后的题面中则可以将转移写成常系数齐次递推的形式了。

    由于上述状态转移方程以常系数齐次递推的形式出现,故我们可以把它写成矩阵的形式,即对于非特殊格子 \(i\)\(\begin{bmatrix}dp_{i+1,0}\\dp_{i+1,1}\\dp_{i+1,2}\end{bmatrix}=\begin{bmatrix}1&0&1\\2&1&2\\1&1&2\end{bmatrix}\begin{bmatrix}dp_{i,0}\\dp_{i,1}\\dp_{i,2}\end{bmatrix}\),对于特殊格子 \(i\)\(\begin{bmatrix}dp_{i+1,0}\\dp_{i+1,1}\\dp_{i+1,2}\end{bmatrix}=\begin{bmatrix}1&0&0\\2&1&0\\1&1&1\end{bmatrix}\begin{bmatrix}dp_{i,0}\\dp_{i,1}\\dp_{i,2}\end{bmatrix}\)。因此最后的 \(dp_n\) 可以写成一排 \(\begin{bmatrix}1&0&1\\2&1&2\\1&1&2\end{bmatrix}\)\(\begin{bmatrix}1&0&0\\2&1&0\\1&1&1\end{bmatrix}\) 连乘的形式,其中 \(\begin{bmatrix}1&0&0\\2&1&0\\1&1&1\end{bmatrix}\) 的个数为 \(m\),显然矩乘算一下就好了,复杂度 \(m\log n\omega^3\),其中 \(\omega=3\)

    #include <bits/stdc++.h>
    using namespace std;
    #define fi first
    #define se second
    #define fill0(a) memset(a,0,sizeof(a))
    #define fill1(a) memset(a,-1,sizeof(a))
    #define fillbig(a) memset(a,63,sizeof(a))
    #define pb push_back
    #define ppb pop_back
    #define mp make_pair
    template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
    template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
    typedef pair<int,int> pii;
    typedef long long ll;
    typedef unsigned int u32;
    typedef unsigned long long u64;
    namespace fastio{
    	#define FILE_SIZE 1<<23
    	char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
    	inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
    	inline void putc(char x){(*p3++=x);}
    	template<typename T> void read(T &x){
    		x=0;char c=getchar();T neg=0;
    		while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
    		while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
    		if(neg) x=(~x)+1;
    	}
    	template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
    	template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
    	void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
    }
    const int MOD=1e9+7;
    int n,m;
    struct mat{
    	ll a[3][3];
    	mat(){memset(a,0,sizeof(a));}
    	mat operator *(const mat &rhs){
    		mat res;
    		for(int i=0;i<3;i++) for(int j=0;j<3;j++) for(int k=0;k<3;k++)
    			res.a[i][j]+=a[i][k]*rhs.a[k][j];
    		for(int i=0;i<3;i++) for(int j=0;j<3;j++) res.a[i][j]%=MOD;
    		return res;
    	}
    };
    int main(){
    	mat x,y,ret;ret.a[0][0]=1;
    	x.a[0][0]=1;x.a[0][1]=0;x.a[0][2]=1;
    	x.a[1][0]=2;x.a[1][1]=1;x.a[1][2]=2;
    	x.a[2][0]=1;x.a[2][1]=1;x.a[2][2]=2;
    	y.a[0][0]=1;y.a[0][1]=0;y.a[0][2]=0;
    	y.a[1][0]=2;y.a[1][1]=1;y.a[1][2]=0;
    	y.a[2][0]=1;y.a[2][1]=1;y.a[2][2]=1;
    	scanf("%d%d",&n,&m);int pre=-1;
    	for(int i=1,v;i<=m;i++){
    		scanf("%d",&v);int stp=v-pre-1;
    		mat z=x;for(;stp;stp>>=1,z=z*z) if(stp&1) ret=z*ret;
    		ret=y*ret;pre=v;
    	} int stp=n-pre-1;
    	mat z=x;for(;stp;stp>>=1,z=z*z) if(stp&1) ret=z*ret;
    	printf("%d\n",ret.a[2][0]);
    	return 0;
    }
    

    upd on 2021.3.9:

    事实上还有一种基于朴素 \(dp\) 的代数优化方法。

    首先我们不考虑组合意义,就直接设 \(dp_i\) 表示考虑前 \(i\) 个格子并在 \(i\)\(i+1\) 放上隔板的答案。显然对于非特殊点 \(dp_i=\sum\limits_{j=0}^{i-1}dp_j(i-j)^2\),否则 \(dp_i=0\)

    考虑直接着手优化这个式子,我们考虑 \(dp_{i+1}\) 的递推式,(这里假设 \(i+1\) 不是标记点,即 \(dp_{i+1}\ne 0\))显然 \(dp_{i+1}=\sum\limits_{j=0}^idp_j(i+1-j)^2\),考虑把最后一项单独提出来,即 \(dp_{i+1}=\sum\limits_{j=0}^{i-1}dp_j(i+1-j)^2+dp_i\),把括号打开即有 \(dp_{i+1}=\sum\limits_{j=0}^{i-1}dp_j(i-j)^2+2\sum\limits_{j=0}^{i-1}dp_j(i-j)+\sum\limits_{j=0}^{i-1}dp_j+dp_i\)

    我们记 \(a_i=\sum\limits_{j=0}^{i-1}dp_j,b_i=\sum\limits_{j=0}^{i-1}dp_j(i-j),c_i=\sum\limits_{j=0}^{i-1}dp_j(i-j)^2\),那么显然 \(dp_i=c_i\),还是考虑上面 \(dp_{i+1}\) 的展开式,那么有 \(dp_{i+1}=a_i+2b_i+c_i+dp_i\),假设我们知道了 \(a_{i},b_{i},c_{i}\),考虑怎样求出 \(a_{i+1},b_{i+1},c_{i+1}\),分 \(i\) 我特殊点和 \(i\) 不是特殊点讨论:

    • \(i\) 不是特殊点,那么
      • \(a_{i+1}=\sum\limits_{j=0}^{i-1}dp_j+dp_{i}=a_i+c_i\)
      • \(b_{i+1}=\sum\limits_{j=0}^{i-1}dp_j(i+1-j)+dp_{i}=\sum\limits_{j=0}^{i-1}dp_j(i-j)+\sum\limits_{j=0}^{i-1}dp_j+dp_i=b_i+a_i+c_i\)
      • \(c_{i+1}=dp_{i+1}=a_i+2b_i+c_i+dp_i=a_i+2b_i+2c_i\)
    • \(i\) 是特殊点,那么
      • \(a_{i+1}=\sum\limits_{j=0}^{i-1}dp_j=a_i\)
      • \(b_{i+1}=\sum\limits_{j=0}^{i-1}dp_j(i+1-j)+dp_{i}=\sum\limits_{j=0}^{i-1}dp_j(i-j)+\sum\limits_{j=0}^{i-1}dp_j=b_i+a_i\)
      • \(c_{i+1}=dp_{i+1}=a_i+2b_i+c_i\)

    上述式子也可写成矩阵的形式,即对于关键点 \(i\)\(\begin{bmatrix}a_{i+1}\\b_{i+1}\\c_{i+1}\end{bmatrix}=\begin{bmatrix}1&0&1\\1&1&1\\1&2&2\end{bmatrix}\times\begin{bmatrix}a_{i}\\b_{i}\\c_{i}\end{bmatrix}\),否则 \(\begin{bmatrix}a_{i+1}\\b_{i+1}\\c_{i+1}\end{bmatrix}=\begin{bmatrix}1&0&0\\1&1&0\\1&2&1\end{bmatrix}\times\begin{bmatrix}a_{i}\\b_{i}\\c_{i}\end{bmatrix}\),这个同样可以矩阵 ksm 计算。时间复杂度同上。

    代码与前一种解法大同小异:

    #include <bits/stdc++.h>
    using namespace std;
    #define fi first
    #define se second
    #define fill0(a) memset(a,0,sizeof(a))
    #define fill1(a) memset(a,-1,sizeof(a))
    #define fillbig(a) memset(a,63,sizeof(a))
    #define pb push_back
    #define ppb pop_back
    #define mp make_pair
    template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
    template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
    typedef pair<int,int> pii;
    typedef long long ll;
    typedef unsigned int u32;
    typedef unsigned long long u64;
    namespace fastio{
    	#define FILE_SIZE 1<<23
    	char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
    	inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
    	inline void putc(char x){(*p3++=x);}
    	template<typename T> void read(T &x){
    		x=0;char c=getchar();T neg=0;
    		while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
    		while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
    		if(neg) x=(~x)+1;
    	}
    	template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
    	template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
    	void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
    }
    const int MOD=1e9+7;
    int n,m;
    struct mat{
    	ll a[3][3];
    	mat(){memset(a,0,sizeof(a));}
    	mat operator *(const mat &rhs){
    		mat res;
    		for(int i=0;i<3;i++) for(int j=0;j<3;j++) for(int k=0;k<3;k++)
    			res.a[i][j]+=a[i][k]*rhs.a[k][j];
    		for(int i=0;i<3;i++) for(int j=0;j<3;j++) res.a[i][j]%=MOD;
    		return res;
    	}
    };
    int main(){
    	mat x,y,ret;ret.a[0][0]=ret.a[1][0]=ret.a[2][0]=1;
    	x.a[0][0]=1;x.a[0][1]=0;x.a[0][2]=1;
    	x.a[1][0]=1;x.a[1][1]=1;x.a[1][2]=1;
    	x.a[2][0]=1;x.a[2][1]=2;x.a[2][2]=2;
    	y.a[0][0]=1;y.a[0][1]=0;y.a[0][2]=0;
    	y.a[1][0]=1;y.a[1][1]=1;y.a[1][2]=0;
    	y.a[2][0]=1;y.a[2][1]=2;y.a[2][2]=1;
    	scanf("%d%d",&n,&m);int pre=0;
    	for(int i=1,v;i<=m;i++){
    		scanf("%d",&v);int stp=v-pre-1;
    		mat z=x;for(;stp;stp>>=1,z=z*z) if(stp&1) ret=z*ret;
    		ret=y*ret;pre=v;
    	} int stp=n-pre-1;
    	mat z=x;for(;stp;stp>>=1,z=z*z) if(stp&1) ret=z*ret;
    	printf("%d\n",ret.a[2][0]);
    	return 0;
    }
    
  • 相关阅读:
    SQL高效运行注意事项(四)
    SQL Serve里DBA要去改变的3个配置选项
    sql还原数据库时候,遇到数据库被占用的解决情况
    sqlserver中将datetime类型转换为yyyyMMddHHmmss格式
    SQL 高效运行注意事项(三)
    当您解开后您从 Internet 上下载的压缩的文件时,文件的修改日期更改为您提取它的日期
    MySQL通过自定义函数实现递归查询父级ID或者子级ID
    YII2集成GOAOP,实现面向方面编程!
    C语言关于指针的注意事项
    转载 could not find a getter for ... in class ... 异常的原因解析
  • 原文地址:https://www.cnblogs.com/ET2006/p/agc013E.html
Copyright © 2011-2022 走看看