Game Relics
题目描述
解法
关键的 \(\tt observation\):由于 \(x\leq c_i\),那么最优策略一定是先疯狂抽卡,然后再买入剩下的东西。这是因为抽到什么其实都是赚的,那么为了让爆率更高一定先抽卡再买卡。
因为在最优策略下买卡的顺序不影响答案,可以把题目等价转化下面的形式:
- 第一类抽奖:如果抽中已经解锁的会花费 \(\frac{x}{2}\),否则会花费 \(x\) 并且解决卡牌 \(i\)
- 第二类抽奖:如果抽中已经解锁的会花费 \(0\),否则会花费 \(c_i\) 并且解锁卡牌 \(i\)
这两类抽奖达到的效果是完全一致的,所以我们只需要取花费的较小者即可。设剩下 \(k\) 张卡牌,代价总和是 \(c\),第一类抽奖新抽到一张牌的期望代价是 \((\frac{n}{k}+1)\cdot\frac{x}{2}\);第二类抽奖新抽到一张牌的期望代价是 \(\frac{c}{k}\)
那么剩下我们只需要计算还剩 \(i\) 张牌,代价总和是 \(j\) 的概率,乘上代价就得到了期望。概率就是对应的方案数除以 \({n\choose i}\),那么用背包计算即可,时间复杂度 \(O(n^2\sum c)\)
总结
将不同的操作转化成相同的形式,便于发现后面的贪心策略;此外这种决策随着随机情况变化的题目,并不一定只能用 \(dp\) 解决。
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 105;
#define db long double
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,a[M];db x,ans,f[M][10005];
int main()
{
n=read();x=read();f[0][0]=1;
for(int i=1;i<=n;i++) m+=a[i]=read();
for(int i=1;i<=n;i++) for(int k=i;k>=1;k--)
for(int j=a[i];j<=m;j++)c++
f[k][j]+=f[k-1][j-a[i]]*1.0*k/(n-k+1);
for(int i=1;i<=n;i++) for(int j=0;j<=m;j++)
ans+=f[i][j]*min((db)j/i,((db)n/i+1)*x/2);
printf("%.10Lf\n",ans);
}
Move by Prime
题目描述
解法
可以把所有质数分开考虑,设质数 \(p\) 的出现次数从大到小排序的结果是 \(x_i\),那么对于一个子序列一定选取其中位数,答案的形式为 \(\sum |x-x_i|\)
容易发现 \(x\) 的正负会被抵消,剩下的部分可以用贡献法来计算。我们考虑 \(x_i\) 的贡献,设排名在 \(i\) 左边的选取了 \(a\) 个,排名在 \(i\) 右边的选取了 \(b\) 个。那么如果 \(a<b\) 贡献 \(x_i\);\(a=b\) 贡献 \(0\);\(a>b\) 贡献 \(-x_i\)
对应的方案数大概是 \(\sum_{a}\sum_{b}{l\choose a}\cdot {r\choose b}={l\choose l-a}\cdot {r\choose b}\),我们利用组合意义就可以将它降维(枚举两边的个数和),设 \(j=i-1-a+b\),那么 \(x_i\) 的贡献就是:
设 \(f_i=\sum_{j=i}^{n-1}{n-1\choose j}-\sum_{j=0}^{i-2}{n-1\choose j}\),这东西可以 \(O(n)\) 预处理出来,然后枚举质数可以做到 \(O(n\log w)\)
更好的做法是对于每种质数,用埃氏筛统计出 \(p^x\) 的倍数个数,那么质数幂次相同的数可以一次计算,把 \(f_i\) 做前缀和就好做了,可以做到 \(O(n+w\log \log w)\)
#include <cstdio>
const int M = 300005;
#define int long long
const int MOD = 1e9+7;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,ans,fac[M],inv[M],w[M],a[M],c[M],f[M],vis[M];
int C(int n,int m)
{
return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
void init(int n)
{
fac[0]=inv[0]=inv[1]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
c[0]=1;
for(int i=1;i<n;i++) c[i]=(c[i-1]+C(n-1,i))%MOD;
for(int i=2;i<=n;i++) f[i]=(f[i-1]+c[n-1]-c[i-1]-c[i-2])%MOD;
}
signed main()
{
n=read();m=300000;init(n);
for(int i=1;i<=n;i++) w[read()]++;
for(int i=2;i<=m;i++) if(!vis[i])
{
for(int j=i;j<=m;j+=i) vis[j]=1;
int j=1,k=1,l=0;
for(;i*k<=m;j++)
{
for(a[j]=0,k*=i,l=k;l<=m;l+=k) a[j]+=w[l];
if(!a[j]) break;
}
for(a[j]=0;--j;)
ans=(ans+j*(f[a[j]]-f[a[j+1]]))%MOD;
}
printf("%lld\n",(ans+MOD)%MOD);
}
New Year and the Tricolore Recreation
题目描述
注意两人操作时都不能只移动中间的棋子。
解法
首先考虑题目给了我们 \(n\) 个独立的子游戏,那么如果我们可以分别求出每个游戏的 \(\tt sg\) 函数就可以解决本题。
考虑 \(\tt Alice\) 如果只移动最左边的棋子,相当于把左边的距离减少 \(d\),如果她移动左边和中间的棋子,相当于把右边的距离减少 \(d\);\(\tt Bob\) 的操作同理。这说明本题其实就是一个公平博弈游戏,每个子游戏等价于有两堆石子 \(w-b-1\) 和 \(r-w-1\)
我们只需要求出 \(sg[i]\) 表示大小为 \(i\) 的石子堆的 \(\tt sg\) 函数值,暴力跑是 \(O(n^2)\) 的。这东西竟然可以用 \(\tt bitset\) 优化,我们对于每个 \(\tt sg\) 值都维护一个 \(\tt bitset\),第 \(i\) 位就表示这个点 存在\(/\)不存在 这个 \(\tt sg\) 值的后继状态。
那么我们从小到大计算 \(\tt sg\) 值,每次把这个点作为后继或到 \(sg[i]\) 的 \(\tt bitset\) 上面去,可以预处理出步数的 \(\tt bitset\) 记为 \(z\)(由质数和两个质数的乘积构成),那么或上 z<<i
即可。
但是这个做法依赖于性质:\(\forall sg[i]\leq 100\)(打表发现,感性理解就是转移较为稀疏),时间复杂度 \(O(\frac{n^2}{w})\)
#include <cstdio>
#include <bitset>
using namespace std;
const int M = 200005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,ans,cnt,vis[M],p[M],dp[M];
bitset<M> b[105],z;
void init(int n)
{
for(int i=2;i<=n;i++)
{
if(!vis[i]) p[++cnt]=i,z[i]=1;
for(int j=1;j<=cnt && i*p[j]<=n;j++)
{
vis[i*p[j]]=1;
if(i%p[j]==0) break;
}
}
for(int i=1;i<=cnt;i++)
for(int j=i;j<=cnt;j++)
{
if(1ll*p[i]*p[j]>n) break;
z[p[i]*p[j]]=1;
}
}
signed main()
{
n=read();m=read();init(2e5);
z[m]=0;m=200000;b[0]=z;
for(int i=1;i<=m;i++)
{
while(b[dp[i]][i]) dp[i]++;
b[dp[i]]|=(z<<i);
}
for(int i=1;i<=n;i++)
{
int a=read(),b=read(),c=read();
ans^=dp[b-a-1];
ans^=dp[c-b-1];
}
if(ans) puts("Alice\nBob");
else puts("Bob\nAlice");
}