zoukankan      html  css  js  c++  java
  • [BZOJ4005][JLOI2015]骗我呢-[dp+容斥]

    Description

    传送门

    Solution

    如果单独考虑一行i,则左边位置的数严格比右边位置的数小。而一行有m个位置,它们可以填[0,m]这m+1个数,则必然有一个数不存在。

    定义第i行的第j位突变需要满足$x[i][j+1]-x[i][j]>1$,此时不存在的数为j。

    通过分析可以得到,假如在i-1行的突变位置为j+1,则第i行突变位置的合法范围为[j,m]。*

    设f[i][j]为在第i行,突变位置为j的情况数。

    则递推式为:$f[i][j]=f[i-1][j+1]+f[i][j-1]$。

    因为当第i-1行在第j+1位突变,第i行的突变位置即为j。f[i][j-1]为在[0,j-1]位突变的情况数,由*可得知这些情况也同样可以在第j位突变。

    特殊的,$f[i][0]=f[i-1][0]+f[i-1][1]$,$f[i][m]=f[i][m-1]$。

    如图,我们把转移画出来后将第i行往右移i-1格并建立虚拟节点来满足f[i][0]的转移。

    图中的n=3,m=3。

    此时的点(n,n+m+1)[即为第n行第n+m个点]表示的并不是所有情况之和,而是第n行突变位置为m的情况数。

    我们考虑多加一行一列,根据递推式,点(n+1,n+m+2)即为第n行所有情况之和了。

    最后将得到图形补全为矩形后是会有n+1行n+m+2列。

    将该图画在平面直角坐标系里(即将其翻转),则终点坐标为(n,n+m+1)

    如图,直线ya=x+1和直线yb=x-(m+2)即为边界。

    我们把多次越过同一边界视为只越过一次。则越界方案为ababa...或babab...。

    则ans=总方案数-第一次越过a的次数-第一次越过b的次数。

    对于先越过a的,考虑:-越界方案末尾为a的情况数+越界方案末尾为ba的方案数-越界方案末尾为aba的方案数。。。直到方案数为0。

    对于越界方案末尾为a的情况数,为将整个图针对直线a翻转后(1,1)到对称终点的方案;当越界方案末尾为ba,则在上一个图的基础上把图沿着直线b翻转,计算(1,1)到本次到对称终点的方案数。

    Code

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    using namespace std;
    typedef long long ll;
    const int mod=1e9+7;
    int n,m;
    ll fac[12000010],inv[12000010];
    void pre()
    {
        fac[0]=inv[0]=1;
        fac[1]=inv[1]=1;
        for (int i=2;i<=12000000;i++)
        {
            fac[i]=fac[i-1]*i%mod;
            inv[i]=(mod-mod/i)*inv[mod%i]%mod;  
        }   
        for (int i=1;i<=12000000;i++) inv[i]=inv[i]*inv[i-1]%mod;
    }
    ll C(int x,int y)
    {
        if (x<y||x<0||y<0) return 0;
        return fac[x]*inv[y]%mod*inv[x-y]%mod;
    }
    ll cal(int x,int y)
    {
        if (x<0||y<0) return 0;return C(x+y,y);
    }
    void turnA(int &x,int &y)
    {
        swap(x,y);
        x--;y++;
    }
    void turnB(int &x,int &y)
    {
        swap(x,y);x+=m+2;y-=m+2;
    }
    ll ans;
    int main()
    {
        scanf("%d%d",&n,&m);
        pre();
        ans=cal(n+m+1,n);
        int x=n+m+1,y=n;
        while (x>=0&&y>=0)
        {
            turnA(x,y);ans=(ans-cal(x,y))%mod;
            turnB(x,y);ans=(ans+cal(x,y))%mod;
        }
        x=n+m+1,y=n;
        while (x>=0&&y>=0)
        {
            turnB(x,y);ans=(ans-cal(x,y))%mod;
            turnA(x,y);ans=(ans+cal(x,y))%mod;
        }
        if (ans<0) ans+=mod;
        cout<<ans;
    }

     

  • 相关阅读:
    设计模式 创建型 单例模式
    设计模式 创建型 抽象工厂模式
    设计模式六大原则
    设计模式 创建型 工厂方法模式
    设计模式 创建型 简单工厂模式
    Junit TDD
    覆盖索引
    多列索引 单列索引
    JDBC PreparedStatement Statement
    常用·SQL
  • 原文地址:https://www.cnblogs.com/coco-night/p/9552677.html
Copyright © 2011-2022 走看看