这是一道难度 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;
}