【题目】
原题地址
求有多少个 n 行 m 列的矩阵满足每个数都在 [0,m] 之间且 ai,j<ai,j+1,ai,j<ai−1,j+1 ,答案模 1e9+7
【解题思路】
这道题想了我好久啊,然后去看了一下PoPoQQQ的题解,一脸要膜拜的样子。转化后的模型在QBXT讲过qwq。
首先考虑这个矩阵本身有什么性质,发现如果我们将每个大小关系连一条边,那么每个点至少在一条长度为 m 的链上,而可供选择的数字大小仅有 m+1 个,也就是说,一条链上最多只有一条边两点权相差为2,其他差值都为1。
考虑转化问题(qwq我不会转化),我们发现实质是是这样一幅图:
求从左下角走到右上角,只能往上或往右走,不碰到两条斜线的方案数。
当然这是 n≤m 的情况,实际上 n>m 时也是一样的,我们都构造 y=x+1 和 y=x−(m+2) 两条“限制线”即可。
(以下这两条直线称作 A 和 B )
那么接下来考虑如何计算,我们可以尝试用容斥原理,即【全集-跨越第一条的方案-跨越第二条的方案+两条都跨越的方案 】。但是这样最后一种是很难计算的。
下面是另一种转化思路:触碰的情况可能非常复杂 比如 ABABBABBBAA 啥的,为了避免重复计数我们把相同的都缩掉变成 ABABABA 这样的串
然后怎么搞呢? 秀操作咯。(接下来的东西我在QBXT是没有认真听的,回来推了好久qwq)
我们令初始点为 (n+m+1,n) ,然后我们做这样的操作:
将当前点沿 A 翻转,然后把原点到当前点的方案数从答案中减去;
将当前点(注意此时已经翻转过了)沿 B 翻转,然后把原点到当前点的方案数从答案中加上;
反复如此直到某一坐标 <0 此时无论如何进行下去方案数都是0了
这样做相当于把以 A 和 A B为后缀的方案删除,然后把以 BA 和 BAB 为后缀的方案加回去,然后把以 ABA 和 ABAB 为后缀的方案删除……
最后我删除的就是以 A 为前缀的所有方案!
然后我们再恢复,先沿 B 翻转再做一次,就删掉了以 B 为前缀的所有方案
这样做时间复杂度是 O(n) 的,写起来也很简单。
还有一种转化方式可以看这里
都好神啊。
【参考程序】
#include
using namespace std;
typedef long long LL;
const int mod=1e9+7;
const int N=3e6+10;
int n,m,x,y,ans;
int fac[N],inv[N];
void init()
{
fac[0]=fac[1]=1;
for(int i=2;i1ll*fac[i-1]*i%mod;
inv[0]=inv[1]=1;
for(int i=2;i1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=2;i1ll*inv[i]*inv[i-1]%mod;
}
int C(int p,int q)
{
if(preturn 0;
return 1ll*fac[p]*inv[q]%mod*inv[p-q]%mod;
}
int calc(int p,int q)
{
if(p<0 || q<0) return 0;
return C(p+q,p);
}
void flip1(int &p,int &q)//flip by y=x+1
{
swap(p,q);
--p;++q;
}
void flip2(int &p,int &q)//flip by y=x-(m+2)
{
swap(p,q);
p+=m+2;q-=m+2;
}
void solve()
{
scanf("%d%d",&n,&m);
ans=calc(n+m+1,n);
x=n+m+1;y=n;
while(x>=0 && y>=0)
{
flip1(x,y);
ans-=calc(x,y);
flip2(x,y);
ans+=calc(x,y);
ans%=mod;
}
x=n+m+1;y=n;
while(x>=0 && y>=0)
{
flip2(x,y);
ans-=calc(x,y);
flip1(x,y);
ans+=calc(x,y);
ans%=mod;
}
ans=(ans+mod)%mod;
printf("%d\n",ans);
}
int main()
{
// freopen("BZOJ4005.in","r",stdin);
// freopen("BZOJ4005.out","w",stdout);
init();
solve();
return 0;
}
【总结】
事实上我至今仍然觉得转化十分神奇,虽然是已经大概搞懂了。
这也说明数学方面的思维还有待提高,和一众神犇的差距还很大qwq。