【LuoguP3266】[JLOI2015]骗我呢

题目链接

题目描述

(懒得写了,见原题)

Sol

一个很神仙的组合问题

稍微模拟就可以发现一个事实,当上一行选择了删去 x x x,后下一行可选的数是 [ x − 1 , m ] [x-1,m] [x1,m]
所以dp也就是一个不断求前缀和并加上后一个的过程: f [ i ] [ j ] = ∑ k = 0 j + 1 f [ i − 1 ] [ k ] f[i][j]=\sum_{k=0}^{j+1}f[i-1][k] f[i][j]=k=0j+1f[i1][k]

之后再往这方面想就比较难有收获了,我们转换一下模型,这个显然类似一个格路问题,只不过允许在某个地方至多一次向下走一格,这样的东西就很不好求。

所以我们必须要把这样的东西转换一下 (无图,以下纯属瞎BB) ,我们画出一个dp的转移DAG图,容易发现其中有一些斜着的边,那么我们把斜着的边给扯直了,这样所有边都在网格上,发现这时我们不能经过两条直线
于是问题转化为求 ( 0 , 0 ) (0,0) (0,0) ( n + m , n ) (n+m,n) (n+m,n)的格路数并且不经过 y = x + 2 y=x+2 y=x+2 y = x − m − 1 y=x-m-1 y=xm1两条直线
然后就很神仙了,容斥计算
答案=总方案 − - 第一个经过了 y = x + 2 y=x+2 y=x+2 − - 第一个经过了 y = x − m − 1 y=x-m-1 y=xm1
考虑怎么求出经过一条直线的方案,常用思想把终点关于该直线对称,这样每一条到这个对称后的终点的路径都对应了一条不合法路径
但是说起来简单实际写起来却比较难理解,每次需要递归翻折才能求出以某个直线开头的方案(其实我也不会啊 TAT , 哪位大佬会的教教我为什么要这么做)

代码:

#include<bits/stdc++.h>
using namespace std;
#define Set(a,b) memset(a,b,sizeof(a))
const int mod=1e9+7;
int n,m;
typedef long long ll;
template<class T>inline void Inc(T&x,int y){x+=y;if(x>=mod) x-=mod;return;}
template<class T>inline void Dec(T&x,int y){x-=y;if(x <  0) x+=mod;return;}
template<class T>inline int fpow(int x,T k){int ret=1;for(;k;k>>=1,x=(ll)x*x%mod)if(k&1) ret=(ll)ret*x%mod;return ret;}
namespace Sol{
    const int N=3e6+1;
    int fac[N],finv[N];
    inline int C(int n,int m){return n<m? 0:(ll)fac[n]*finv[m]%mod*finv[n-m]%mod;}
    inline void Sieve(){
        fac[0]=finv[0]=1;
        for(int i=1;i<N;++i) fac[i]=(ll)fac[i-1]*i%mod;
        finv[N-1]=fpow(fac[N-1],mod-2);
        for(int i=N-2;i;--i) finv[i]=(ll)finv[i+1]*(i+1)%mod;
        return;
    }
    inline void Flip(ll&x,ll&y,ll c){ll dt=x+c-y;x=x-dt;y=y+dt;return;}
    void work(){
        Sieve();
        int ans=C(m+(n<<1),n);
        ll x,y,op;
        x=n+m,y=n;op=-1;
        while(233) {
            op==1? Flip(x,y,2):Flip(x,y,-1-m);
            if(x<0||y<0) break;
            op==1? Inc(ans,C(x+y,x)):Dec(ans,C(x+y,x));
            op=-op;
        }
        x=n+m,y=n,op=-1;
        while(233){
            op==1? Flip(x,y,-1-m):Flip(x,y,2);
            if(x<0||y<0) break;
            op==1? Inc(ans,C(x+y,x)):Dec(ans,C(x+y,x));
            op=-op;
        }
        cout<<ans<<endl;
    }
}
int main()
{
    cin>>n>>m;
    Sol::work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值