zoukankan      html  css  js  c++  java
  • 【bzoj4872】[Shoi2017]分手是祝愿 期望dp

    Description

    Zeit und Raum trennen dich und mich.

    时空将你我分开。B 君在玩一个游戏,这个游戏由 n 个灯和 n 个开关组成,给定这 n 个灯的初始状态,下标为

    从 1 到 n 的正整数。每个灯有两个状态亮和灭,我们用 1 来表示这个灯是亮的,用 0 表示这个灯是灭的,游戏

    的目标是使所有灯都灭掉。但是当操作第 i 个开关时,所有编号为 i 的约数(包括 1 和 i)的灯的状态都会被

    改变,即从亮变成灭,或者是从灭变成亮。B 君发现这个游戏很难,于是想到了这样的一个策略,每次等概率随机

    操作一个开关,直到所有灯都灭掉。这个策略需要的操作次数很多, B 君想到这样的一个优化。如果当前局面,

    可以通过操作小于等于 k 个开关使所有灯都灭掉,那么他将不再随机,直接选择操作次数最小的操作方法(这个

    策略显然小于等于 k 步)操作这些开关。B 君想知道按照这个策略(也就是先随机操作,最后小于等于 k 步,使

    用操作次数最小的操作方法)的操作次数的期望。这个期望可能很大,但是 B 君发现这个期望乘以 n 的阶乘一定

    是整数,所以他只需要知道这个整数对 100003 取模之后的结果。

    Input

    第一行两个整数 n, k。

    接下来一行 n 个整数,每个整数是 0 或者 1,其中第 i 个整数表示第 i 个灯的初始情况。

    1 ≤ n ≤ 100000, 0 ≤ k ≤ n;

    Output

    输出一行,为操作次数的期望乘以 n 的阶乘对 100003 取模之后的结果。

    Sample Input

    4 0
    0 0 1 1

    Sample Output

    512

    Sol

    作为一道联考省选题,覆盖知识点广,题目又着切合实际的背景,解法比较自然,给出题人点赞!

    题确实挺好的,但是我太菜了模拟赛的时候直接模拟骗了80分......后来听了讲解才补的......

    我们发现每个灯只用按一次,而每个灯的贡献又是独一无二的,所以需要按的灯的集合是固定的,每次我们只关心按的灯对于剩余步数的贡献,设(f[i])表示还需要i步才能全部按灭的期望步数,则(f[i]=frac{i}{n}f[i-1]+frac{n-i}{n}f[i+1]+1),也就是分两种情况:按的灯在不在有用集合里面。

    这个式子并不需要高斯消元,有两种做法:

    1.因为f[0]是确定的,所以可以用回代法求出所有的值,时间复杂度(O(n))

    2.设(g[i]=f[i]-f[i-1]),表示从需要i步到需要i-1步的期望次数,那么:

    (f[i]-frac{i}{n}f[i-1]=frac{n-i}{n}f[i+1]+1)

    (f[i]-f[i-1]=frac{n-i}{n}(f[i+1]-f[i-1])+1)

    (g[i]=frac{n-i}{i}((f[i+1]-f[i])+(f[i]-f[i-1]))+1)

    (g[i]=frac{n-i}{i}(g[i+1]+g[i])+1)

    (frac{i}{n}g[i]=1+frac{n-i}{n}g[i+1])

    (g[i]=frac{n+(n-i)g[i+1]}{i})

    然后就可以(O(n))计算了。

    算出来以后特判一下(n=k)以及(k>=tot)的情况(tot为有用的灯集合大小),这样的情况下答案直接是tot。

    否则答案(=(sum_{i=k+1}^{tot}g[i])+k)

    Code

    #include <bits/stdc++.h>
    using namespace std;
    int n,a[100005],f[100005],tot,K,fac=1,P=100003,ans;vector<int>e[100005];
    int ksm(int a,int b){int res=1;for(;b;b>>=1,a=1ll*a*a%P) if(b&1) res=1ll*res*a%P;return res;}
    int main()
    {
        for(int i=1;i<=P-3;i++) for(int j=i;j<=P-3;j+=i) e[j].push_back(i);
        scanf("%d%d",&n,&K);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=n;i;i--)
        {
            if(a[i]) tot++;else continue;
            for(int j=0;j<e[i].size();j++) a[e[i][j]]^=1;
        }
        for(int i=1;i<=n;i++) fac=1ll*fac*i%P;
        for(int i=n;i;i--) f[i]=1ll*(n+1ll*(n-i)*f[i+1]%P)%P*ksm(i,P-2)%P;
        if(K==n||K>=tot) ans=tot;
        else for(int i=tot;i>K;i--) ans=(ans+f[i]+(i==tot?K:0))%P;
        printf("%d
    ",1ll*ans*fac%P);
    }
    
  • 相关阅读:
    监控里的主码流和子码流是什么意思
    监控硬盘容量计算
    一个能让你了解所有函数调用顺序的Android库
    电工选线
    oracle linux dtrace
    list all of the Oracle 12c hidden undocumented parameters
    Oracle Extended Tracing
    window 驱动开发
    win7 x64 dtrace
    How to Use Dtrace Tracing Ruby Executing
  • 原文地址:https://www.cnblogs.com/CK6100LGEV2/p/9408265.html
Copyright © 2011-2022 走看看