• 【ACM组合数学 | 错排公式】写信


    题目链接:https://ac.nowcoder.com/acm/contest/54484/B

    题意很简单,但是数据范围偏大。

    错排公式

    首先来推导一下错排公式:

    D(n)=n!k=0n(1)kk!

    设一个函数:

    Sipi=i

    那么我们可以知道:

    D(n)=n!|i=1nSi|

    这个表示所有方案数减去至少有一个位置放对的方案数

    现在来考虑一下如何处理后面这个并集,并集往往是不好求的,而交集会好求很多,所以在求并集的时候我们往往采取容斥原理将一个并集转换成诸多交集的加减运算

    我们用一个图可以来表示当n = 3的情况:

    其中有:

    |S1S2S3|=|S1|+|S2|+|S3||S1S2||S1S3||S2S3|+|S1S2S3|

    扩展一下就可以得到下面的柿子:

    |i=1nSi|=k=1n(1)k1i1i2...ikn|Si1Si2...Sik|

    然后有:

    1i1i2...ikn|Si1Si2...Sik|=Cnk(nk)!

    这个表示啥呢,左边这个柿子的含义其实是i1 ~ ik都放对了,其他位置上无所谓的方案数,就等同于在n个位置中选择k个放对,剩下的随便放的方案数。

    所以可得下面的柿子:

    |i=1nSi|=k=1n(1)kCnk(nk)!

    然后化简得:

    |i=1nSi|=k=1n(1)kn!k!

    然后代回到一开始的答案表达式中:

    D(n)=n!k=1n(1)kn!k!

    n!提出来,再化简一下得到:

    D(n)=n!k=0n(1)kk!

    回到本题

    但是有这个柿子依然不好写这题,这题如果是1e7就可以直接O(n)写了,但是这题是1e9的数据范围,可以考虑一下分段打表(一般要求函数可以递推),但是这个表达式好像不是很好打,我们来分析一下。

    首先网上有一个比较有名递推式(证明略):

    D(n)=(n1)[D(n1)+D(n2)]

    这个递推需要用到前两项,也就是说我们需要打两个表,然后才可以做,有点麻烦,但是其实是可以只用一项的。

    我看网路上都没有用下面这种方式递推的,我在这里写一下。

    我们考虑D(n) -> D(n + 1)这样的转移:

    D(n)=n!k=0n(1)kk!

    D(n+1)=(n+1)!k=0n+1(1)kk!=(n+1)![k=0n(1)kk!+(1)n+1(n+1)!]=(n+1)!k=0n(1)kk!+(1)n+1=(n+1)×n!k=0n(1)kk!+(1)n+1=(n+1)×D(n)+(1)n+1

    然后令段大小T = 1e7打表打出D(0), D(T), D(2T) ... D(100T)就好了。

    最终的复杂度是O(n)但是常数极小,所以可以过。

    Code:

    #include 
    #define int long long
    using namespace std;
    
    const int p = 1e9 + 7, T = 1e7;
    
    int a[110] =
    {
    1,824182295,933713113,474482547,930651136,251064654,637937211,229643390,307311871,448853213,
    322273426,398890147,194914852,884947442,154199209,881788023,389699639,733217502,601739182,
    372305477,213823357,713959988,498202615,196342945,324300550,154001751,974475946,540773759,
    467881322,257531902,598680559,367927849,971346692,94577421,617165552,128327758,503709458,
    253566817,820144401,13965056,82358069,805941568,533047638,69430220,686678173,297170813,
    34546238,323435423,499126069,487532712,468899710,790590914,581347156,955359050,700529992,
    518280890,98592091,64544225,988209678,422603955,40661679,174468756,573631136,757555557,
    710709955,775098981,499158883,969149294,880429710,42564126,333697951,522067888,579797877,
    528967798,717694718,309384913,31308092,316850320,220854491,878646494,963974981,377654637,
    705101053,542246848,466289530,750036412,819636314,688721174,464087273,517164631,256789690,
    482685016,276682441,473333947,340221393,762927538,624766601,984537252,977632075,34192646,
    402182971,977005016
    };
    
    int mo(int x){return (x % p + p) % p;}
    
    void solve()
    {
    	int n;cin >> n;
    	int ans = a[n / T];
    	for(int i = n / T * T + 1;i <= n; ++ i)ans = mo(ans * i % p + ((i & 1) ? -1 : 1));
    	cout << ans << '\n';
    }
    
    
    void table()
    {
    	int x = 1;//d(0) = 1,这个有点特殊
        cout << x << ",";
        int cnt = 1;
        for(int i = 1;i <= 1e9; ++ i)
        {
            x = x * i % p;
            if(i & 1)x = (x - 1 + p) % p;
            else x = (x + 1) % p;
            
            if(i % T == 0)
            {
            	cout << x << ",";
        		cnt ++;
        	}
        	
            if(cnt % 10 == 0)
            {
            	cout << '\n';
            	cnt = 1;
            }
            
        }
    }
    
    signed main()
    {
        table();
    	solve();
    	//return 0;
    }
    
  • 相关阅读:
    什么是Jmeter ?Jmeter使用的原理步骤是什么?
    SpringMVC 域对象共享数据
    智能网联-最全术语
    Lambda表达式和Stream API (尚硅谷)
    FPGA实战2-数码管实验verilog
    综合布线工程测试技术
    SPA项目开发之首页导航+左侧菜单
    android 12 framework开发第53节-Activity的reLaunch及onConfigurationChanged android源码分析
    Cadence OrCAD Capture 如何在原理图中设置网络的飞线拓扑结构
    #include <> 与 #include “ “ : 尖括号和双撇号的区别、何时用
  • 原文地址:https://www.cnblogs.com/eriktse/p/17325308.html