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.逆推的本质

浙公网安备 33010602011771号