Loading

P3750

P3750
太神了,无限考虑列期望状态方程。
考虑逆推,记\(dp_i\)表示还有i个灯亮着的期望操作次数。不好做,因为不知道亮着是哪个灯。
弱化限制,考虑如果要求最小操作次数怎么求,贪心的做,从大到小考虑,亮着的就操作。容易看出一个性质,对于一盏灯其贡献没法被其他灯组合所替代,回到dp,换一种记录状态方式,记\(dp_i\)表示还有i个开关要按的期望操作次数,相比于上一种状态,不需要知道亮那个灯,同时有良好的转移顺序,\(dp_{i}\)->\(dp_{i-1}\)那么更据上面性质,分类讨论

  • 按对了开关,\(\frac{i}{n}dp_{i}+1->dp_{i-1}\)
  • 没按对开关,\(\frac{n-i}{n}dp_{i}+1->dp_{i+1}\)
    因为没按对开关,还要按回来

\[dp_{i}=\frac{i+1}{n}dp_{i+1}+1+\frac{n-i-1}{n}dp_{i-1}+1 \]

然后,高斯消元?肯定不是啊,复杂度都不对。
这个转移顺序也好不多。
再换一种,考虑计算增量,\(dp_i\)表示还有i个开关要按的,转移到i-1期望操作次数。那么同样分类

  • 按对了开关,\(dp_{i}=\frac{i}{n}\)
  • 没按对开关,\(dp_{i}=\frac{n-i}{n}(dp_{i+1}+dp_{i}+1)\)

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

现在问题是从那个方向开始推,如果从小往大推,虽然知道dp[0],但是注意到\(dp_{1}\)会依赖\(dp_{n}\),违反了增量使用条件,那么只能从N->1推。
而恰好N这个状态是一定的,所以从N->1推。
注意我们先前算的最小操作次数是有用的,因为这个k,所以将[K,tot]dp值加起来再+k,别忘乘上n!
Q:不是说期望递推方程一般要逆推吗。
A:事实上逆推的定义指的是:状态定义是到终态的期望,和转移方向无关
事实上,这题的k是对于计算增量的提示,遇见期望到一般转为最优解,考虑计算增量

code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=100003,N_ = 1e5+10;;
ll ans,tot,H[N_],a[N_],b[N_],dp[N_],K,N;
vector<ll> q[N_];
inline ll qp(ll x,ll y){
    ll res=1;
    while(y)
    {
        if(y&1)res=res*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return res;
}
int main()
{
    cin>>N>>K;
    H[0]=1;
    for(int i=1;i<=N;i++)H[i]=H[i-1]*1ll*i%mod;
    for(int i=1;i<=N;i++)
        for(int j=1;j*i<=N;j++)q[i*j].push_back(i);
    for(int i=1;i<=N;i++)cin>>a[i];
    for(int i=1;i<=N;i++)b[i]=a[i];
    for(int i=N;i>=1;i--)
        if(b[i]){
            tot++;
            for(auto d:q[i])b[d]^=1;
        }    
    dp[N]=1;
    for(int i=N-1;i>=0;i--)
        dp[i]=((N-i)*dp[i+1]%mod+N)%mod*qp(i,mod-2)%mod;
    if(K>=tot)cout<<tot*H[N]%mod<<'\n',exit(0);
    else {
        for(int i=K+1;i<=tot;i++)ans+=dp[i];
        cout<<((ans+K)*1ll*H[N]+mod)%mod;
    }
    return 0;
}

小结:1.优化dp本质->优化枚举顺序/对象。2.遇见期望到一般转为最优解,考虑计算增量.3.逆推的本质

posted @ 2026-07-30 20:29  leaf_ydc  阅读(4)  评论(0)    收藏  举报