[ABC417F] Random Gathering
题面- [ABC417F] Random Gathering
算法关键词:数学,模拟,线段树
题目大意:有 \(N\) 个盘子,初始第 \(i\) 个盘子处有 \(A_i\) 个石子。有 \(M\) 次操作,每次操作给出一个 \(l\) 和一个 \(r\) ,要求把 \(l\) 到 \(r\) 之间所有石子挪到 \(l\) 到 \(r\) 之间随机一个盘子上。问最后每个盘子里石子的期望数量。
思路1
没有思路1,因为看到这道题需要期望值和随机(虽然没怎么用到),毫无头绪。那么直接看正解思路。
思路2(正解)
这个题看起来很麻烦,不妨从最直接的模拟开始。
其中一个难点(似乎)在于期望值的计算,其实很简单,即 \(所有石子数\div区间\)(即\(r-l+1\))。而另一个难点在于取模,因为取模关于期望值所以需要用到关于逆元和费马小定理的知识也是我最不会的的知识。
最后是最简单 (并非) 的区间修改,取模操作。事实上取模和加减乘除一样可以用线段树实现,真是好消息!
恶补了一下关于逆元的知识后,发现这部分只需要用一个快速幂就可以解决。
线段树根本没有被削弱!写了半天的代码。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
struct node{
int s,laz,sz;
}t[1000005];
ll n,m,a[200005];
ll ksm(int a,int b){
int as=1,bs=a;
while(b){
if(b&1)as=as*bs%mod;
bs=bs*bs%mod;
b>>=1;
}return as;
}
void update(int nw){
t[nw].s=(t[(nw<<1)].s+t[(nw<<1|1)].s)%mod;
t[nw].sz=t[(nw<<1)].sz+t[(nw<<1|1)].sz;
}
void pushdown(int nw){
if(t[nw].laz){
t[(nw<<1)].s=t[nw].laz*t[(nw<<1)].sz;
t[(nw<<1|1)].s=t[nw].laz*t[(nw<<1|1)].sz;
t[(nw<<1)].laz=t[(nw<<1|1)].laz=t[nw].laz;
t[nw].laz=0;
}
}
void build(int nw,int l,int r){
if(l==r){
t[nw].sz=1;
t[nw].s=a[l];
return;
}
build((nw<<1),l,((l+r)>>1));
build((nw<<1|1),((l+r)>>1)+1,r);
update(nw);
}
void mak(int nw,int l,int r,int x,int y,int k){
if(x<=l&&r<=y){
t[nw].s=k*t[nw].sz;
t[nw].laz=k;
return;
}
pushdown(nw);
if(x<=((l+r)>>1)) mak((nw<<1),l,((l+r)>>1),x,y,k);
if(y>((l+r)>>1)) mak((nw<<1|1),((l+r)>>1)+1,r,x,y,k);
update(nw);
}
ll ask(int nw,int l,int r,int x,int y){
if(x<=l&&r<=y){
return t[nw].s;
}
pushdown(nw);
int sum=0;
if(x<=((l+r)>>1))sum=(sum+ask((nw<<1),l,((l+r)>>1),x,y))%mod;
if(y>((l+r)>>1))sum=(sum+ask((nw<<1|1),((l+r)>>1)+1,r,x,y))%mod;
return sum;
}
int main() {
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
build(1,1,n);
while(m--){
int l,r;
cin>>l>>r;
ll sum=ask(1,1,n,l,r);
mak(1,1,n,l,r,sum*ksm(r-l+1,mod-2)%mod);
}
for(int i=1;i<=n;i){++
cout<<ask(1,1,n,i,i)<<" ";
}
return 0;
}
浙公网安备 33010602011771号