感觉无从下手的概率题。
想清楚本质并不难。
瞎按不好考虑。反正有一般的是要直接做的。就考虑最优情况怎么按。
一个灯最多按一次。所以,最优策略次数一定小于等于n
从大到小依次按灭即可。把约数改变状态。
约数处理,枚举i从1~n,枚举倍数加入vector O(nlogn)
(听说可以拿到80pts了。。)
随机的部分怎么办?
发现,最优的策略一定要按特定的几次。并且这些次数按的先后顺序其实没有关系。
最优情况需要按的次数从多到少,所以,这就有了topo的性质!
而且,一旦最优策略的剩下的步数确定,答案的期望步数就确定了。与具体局面无关(并不关系到底最优策略怎么操作)!!
事情就比较好办了。
假如设f[i]表示,最优策略还要进行i次操作时,到终点的次数。
然而转移成环,而且没有办法破环(转化成kx+b的线性式可以,但是递推项在分母位置,没有办法取模)
所以,就一步一步来,设f[i]表示,最优策略还要进行i次操作时,变成操作i-1次期望次数。
f[i]=i/n*1+(n-i)/n*(f[i]+f[i+1]+1)
因为,没有先后顺序,所以i个中直接选择哪一个都可以。转移没有问题。
移项之后,即可倒序递推求解。
f[n]=1 假如剩下n步的话,那必然每个都要按一次就是最优解,所以,无论怎么按,都能到下一层。
最后,选取need~k这一段的f值。再加上k,再处理阶乘。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=100000+5; const int mod=100003; int n,k; vector<int>mem[N]; int inv[N]; ll f[N]; int a[N]; int main(){ scanf("%d%d",&n,&k); for(int i=1;i<=n;i++)scanf("%d",&a[i]); for(int i=1;i<=n;i++) for(int j=i;j<=n;j+=i) mem[j].push_back(i); inv[1]=1; for(int i=2;i<=n;i++) inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod; int nd=0; for(int i=n;i>=1;i--){ if(a[i]){ for(int j=0;j<(int)mem[i].size();j++){ a[mem[i][j]]^=1; } nd++; } } ll ans=0; //cout<<nd<<endl; if(nd<=k){ ans=nd; } else{ f[n]=1; for(int i=n-1;i>=1;i--) f[i]=((ll)n+(ll)(n-i)*f[i+1]%mod)%mod*inv[i]%mod; for(int i=nd;i>k;i--) ans=(ans+f[i])%mod; ans=(ans+k)%mod; } for(int i=1;i<=n;i++) ans=(ans*i)%mod; printf("%lld",ans); return 0; }
总结:
1.发现最优策略,发现操作次数一定,答案就一定。所以可以就着最优操作次数下手。(亮灯的数量显然不行。因为变化太大。)这样设计也满足选中就进入下一层,也可能原地不动或者返回上一层。
2.状态设计不一定要直接指向终点,如果后继比较显然的话,可以尝试这样设计状态。