【组合数学+转化问题】BZOJ4005[JLOI2015]骗我呢

数论-组合数学 专栏收录该内容
9 篇文章 0 订阅

【题目】
原题地址
求有多少个 n m列的矩阵满足每个数都在 [0,m] 之间且 ai,j<ai,j+1,ai,j<ai1,j+1 ,答案模 1e9+7

【解题思路】
这道题想了我好久啊,然后去看了一下PoPoQQQ的题解,一脸要膜拜的样子。转化后的模型在QBXT讲过qwq。

首先考虑这个矩阵本身有什么性质,发现如果我们将每个大小关系连一条边,那么每个点至少在一条长度为 m 的链上,而可供选择的数字大小仅有m+1个,也就是说,一条链上最多只有一条边两点权相差为2,其他差值都为1

考虑转化问题(qwq我不会转化),我们发现实质是是这样一幅图:
这里写图片描述
求从左下角走到右上角,只能往上或往右走,不碰到两条斜线的方案数。

当然这是 nm 的情况,实际上 n>m 时也是一样的,我们都构造 y=x+1 y=x(m+2) 两条“限制线”即可。
(以下这两条直线称作 A B

那么接下来考虑如何计算,我们可以尝试用容斥原理,即【全集-跨越第一条的方案-跨越第二条的方案+两条都跨越的方案 】。但是这样最后一种是很难计算的。

下面是另一种转化思路:触碰的情况可能非常复杂 比如 ABABBABBBAA 啥的,为了避免重复计数我们把相同的都缩掉变成 ABABABA 这样的串
然后怎么搞呢? 秀操作咯。(接下来的东西我在QBXT是没有认真听的,回来推了好久qwq)
我们令初始点为 (n+m+1,n) ,然后我们做这样的操作:
将当前点沿 A 翻转,然后把原点到当前点的方案数从答案中减去;
将当前点(注意此时已经翻转过了)沿B翻转,然后把原点到当前点的方案数从答案中加上;
反复如此直到某一坐标 <0 <script type="math/tex" id="MathJax-Element-541"><0</script> 此时无论如何进行下去方案数都是0了
这样做相当于把以 A AB为后缀的方案删除,然后把以 BA BAB 为后缀的方案加回去,然后把以 ABA ABAB 为后缀的方案删除……
最后我删除的就是以 A 为前缀的所有方案!
然后我们再恢复,先沿B翻转再做一次,就删掉了以 B 为前缀的所有方案
这样做时间复杂度是O(n)的,写起来也很简单。

还有一种转化方式可以看这里
都好神啊。

【参考程序】

#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。

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值