(sf E-Crypto Lights)
题目
给定长度为 (n) 的初始均为 (0) 的序列,你有如下操作:
- 在 值为 (0) 的点之间等概率选择一个点变成 (1)。
- 如果有两个值为 (1) 的点距离 (<k),结束操作。否则回到
1.
。
你需要求出最终序列 (1) 个数的期望。
解法
首先,“最终序列 (1) 个数的期望” 等价于 “结束时操作次数期望”。
这样不太好算,因为 “结束时” 和 “结束前” 两个状态不太一样。于是我们可以再进行一步转化:结束前操作次数期望 (+1)。
钦定结束前操作次数为 (i),那么到达每个操作次数为 (i) 状态的概率是 (frac{1}{n imes (n-1) imes... imes (n-i+1)}) 也即 (frac{(n-i)!}{n!})。
那么如何计算合法的状态呢?考虑如果没有 (k) 的限制,合法种类数就是 ( ext{C}_{n}^i imes i!)。有一个奇妙的发现,就是合法状态每两个 (1) 之间必定有至少 (k-1) 个 (0)!那么类似小球入盒的解决方案,我们把每两个 (1) 之间一定有的 (k-1) 个 (0) 先抛掉,那么就剩下 (n-(k-1)(i-1)) 个位置,我们在里面任选 (i) 个位置,再在每两个 (1) 之间插入 (k-1) 个 (0)。容易发现这是等价于原问题的,因为一段 (0) 的相对位置对方案没有影响。
所以有:
(sf G - Try Booking)
解法
既然要求互不相交,你会发现长度 (x) 最多只能选 (frac{n}{x}) 个区间。所以惊喜地发现总区间个数是 (sum_{x=1}^n frac{n}{x}approx nln n)。
对于每个 (x),如果我们找到编号最小的区间 ([l,r]),那么接下来需要在 ([1,l-1]cup [r+1,n]) 中寻找编号最小的区间… 以此类推。
问题转化为在查询区间中找编号最小的区间。不过这不能用线段树维护,因为线段树可能会出现查询区间与小区间相交的情况。
我想用 (mathtt{cdq}) 分治!但是注意到此题的询问区间是动态的,必须询问到上一个编号最小的区间才能确定下一个询问。
那就… 树套树!
代码
(sf H - Hopping Around the Array)
解法
首先发现设计关于 (i ightarrow j) 的最小步数的状态是不现实的,它的范围高达 (4e8)。既然询问只问 ((l_i,r_i,k_i)),为什么不试试类似分块的做法,一块块地拼凑出答案?
事实上,这个问题可以用倍增巧妙地解决。
设 (dp_{k,i,j}) 表示删除不超过 (k) 个点,从 (i) 开始走 (2^j) 步能到达的最远位置。
由于 (k) 比较小,我们可以枚举 (k_1,k_2) 来进行转移:
具体可以用 (mathtt{st}) 表来实现。
如何统计答案?想想我们是如何用倍增求解 ( m lca) 的,这里也类似:从大到小枚举 (2^j) 步,用 (mathtt{st}) 表维护对应步数 (dp) 值的区间 (max)。对于每个询问,定义 (f_k) 为删除不超过 (k) 个点从 (l_i) 能走到的最远的小于 (r_i) 的位置,每枚举一个步数就用类似于上文的转移来转移 (f) 值,如果此时 (f_{k_i}<r_i) 就说明可以走这个步数,否则就将 (f) 回退到原来的状态。
最后将每个询问的步数 (+1) 就是答案。
整体时间复杂度约为 (mathcal O(900 imes nlog n))。
代码
下次高维 (mathtt{dp}) 再不按循环顺序来我就是 (mathtt{sb})。
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC target("avx")
#pragma GCC optimize("Ofast")
#include <cstdio>
#define rep(i,_l,_r) for(signed i=(_l),_end=(_r);i<=_end;++i)
#define fep(i,_l,_r) for(signed i=(_l),_end=(_r);i>=_end;--i)
#define print(x,y) write(x),putchar(y)
template <class T> inline T read(const T sample) {
T x=0; int f=1; char s;
while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
return x*f;
}
template <class T> inline void write(const T x) {
if(x<0) return (void) (putchar('-'),write(-x));
if(x>9) write(x/10);
putchar(x%10^48);
}
template <class T> inline T Max(const T x,const T y) {if(x>y) return x; return y;}
template <class T> inline T Min(const T x,const T y) {if(x<y) return x; return y;}
const int maxn=2e4+4;
int n,q,lg[maxn],a[maxn],st[32][18][maxn],dp[18][32][maxn],ans[maxn],f[maxn][32],tmp[32];
struct node {
int l,r,k;
} s[maxn];
void init() {
rep(i,2,n) lg[i]=lg[i>>1]+1;
}
int Query(int k,int l,int r) {
int d=lg[r-l+1];
return Max(st[k][d][l],st[k][d][r-(1<<d)+1]);
}
int main() {
n=read(9),q=read(9); init();
int lgn=lg[n]+1;
rep(i,1,n) a[i]=read(9);
rep(k,0,30) rep(i,1,n) dp[0][k][i]=Min(n,i+a[i]+k);
rep(j,1,lgn) {
rep(k,0,30) rep(i,1,n) st[k][0][i]=dp[j-1][k][i];
rep(k,0,30) rep(i,1,lgn) rep(p,1,n)
if(p+(1<<i)-1<=n)
st[k][i][p]=Max(st[k][i-1][p],st[k][i-1][p+(1<<i-1)]);
else break;
rep(k1,0,30) rep(k2,0,30) {
if(k1+k2>30) break;
rep(i,1,n) dp[j][k1+k2][i]=Max(dp[j][k1+k2][i],Query(k1,i,dp[j-1][k2][i]));
}
}
rep(i,1,q) {
s[i].l=read(9),s[i].r=read(9),s[i].k=read(9);
if(s[i].l==s[i].r) ans[i]=-1;
else rep(j,0,s[i].k) f[i][j]=s[i].l;
}
fep(j,lgn,0) {
rep(k,0,30) rep(i,1,n) st[k][0][i]=dp[j][k][i];
rep(k,0,30) rep(i,1,lgn) rep(p,1,n)
if(p+(1<<i)-1<=n)
st[k][i][p]=Max(st[k][i-1][p],st[k][i-1][p+(1<<i-1)]);
else break;
rep(i,1,q) {
if(ans[i]==-1) continue;
rep(k,0,s[i].k) tmp[k]=0;
rep(k1,0,s[i].k) rep(k2,0,s[i].k) {
if(k1+k2>s[i].k) break;
tmp[k1+k2]=Max(tmp[k1+k2],Query(k1,s[i].l,f[i][k2]));
}
if(tmp[s[i].k]<s[i].r) {
ans[i]+=(1<<j);
rep(k,0,s[i].k) f[i][k]=tmp[k];
}
}
}
rep(i,1,q) print(ans[i]+1,'
');
return 0;
}