【题目】
原题地址
求有多少个
n
行
【解题思路】
这道题想了我好久啊,然后去看了一下PoPoQQQ的题解,一脸要膜拜的样子。转化后的模型在QBXT讲过qwq。
首先考虑这个矩阵本身有什么性质,发现如果我们将每个大小关系连一条边,那么每个点至少在一条长度为
m
的链上,而可供选择的数字大小仅有
考虑转化问题(qwq我不会转化),我们发现实质是是这样一幅图:
求从左下角走到右上角,只能往上或往右走,不碰到两条斜线的方案数。
当然这是
n≤m
的情况,实际上
n>m
时也是一样的,我们都构造
y=x+1
和
y=x−(m+2)
两条“限制线”即可。
(以下这两条直线称作
A
和
那么接下来考虑如何计算,我们可以尝试用容斥原理,即【全集-跨越第一条的方案-跨越第二条的方案+两条都跨越的方案 】。但是这样最后一种是很难计算的。
下面是另一种转化思路:触碰的情况可能非常复杂 比如
ABABBABBBAA
啥的,为了避免重复计数我们把相同的都缩掉变成
ABABABA
这样的串
然后怎么搞呢? 秀操作咯。(接下来的东西我在QBXT是没有认真听的,回来推了好久qwq)
我们令初始点为
(n+m+1,n)
,然后我们做这样的操作:
将当前点沿
A
翻转,然后把原点到当前点的方案数从答案中减去;
将当前点(注意此时已经翻转过了)沿
反复如此直到某一坐标
<0
<script type="math/tex" id="MathJax-Element-541"><0</script> 此时无论如何进行下去方案数都是0了
这样做相当于把以
A
和
最后我删除的就是以
A
为前缀的所有方案!
然后我们再恢复,先沿
这样做时间复杂度是
还有一种转化方式可以看这里
都好神啊。
【参考程序】
#include<bits/stdc++.h>
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;i<N;++i)
fac[i]=1ll*fac[i-1]*i%mod;
inv[0]=inv[1]=1;
for(int i=2;i<N;++i)
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=2;i<N;++i)
inv[i]=1ll*inv[i]*inv[i-1]%mod;
}
int C(int p,int q)
{
if(p<q) return 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。