UR #19
前进四
题目描述
解法
翻译一下,题目就是让你求从后往前的极长下降子序列的长度,可以直接上 \(O(n\log^2 n)\) 的板子。
这种优秀的做法在本题的环境下还是行不通,我们原来是依次扫描询问,维护每个位置的数据结构。我们换一种思路,依次从后往前依次扫描位置,维护每个询问的数据结构。
我们把所有东西离线下来,那么修改变成了对一段询问区间取 \(\min\),询问变成了对于某个询问单点求 \(\min\) 值的变化次数。
可以直接用势能线段树维护,在修改的时候顺便记一个变化次数的标记即可,时间复杂度 \(O(n\log n)\)
总结
神奇的离线方式增加了!以离线的方法来达到切换主体的目的。
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 1000005;
const int N = M<<2;
#define pb push_back
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[M],s[N],mx[N],cx[N];
vector<pair<int,int> > a[M];vector<int> q[M];
void down(int i)
{
if(s[i])
{
if(mx[i]<mx[i<<1])
mx[i<<1]=mx[i],s[i<<1]+=s[i];
if(mx[i]<mx[i<<1|1])
mx[i<<1|1]=mx[i],s[i<<1|1]+=s[i];
s[i]=0;
}
}
void up(int i)
{
mx[i]=max(mx[i<<1],mx[i<<1|1]);
cx[i]=max(cx[i<<1],cx[i<<1|1]);
if(mx[i]!=mx[i<<1]) cx[i]=max(cx[i],mx[i<<1]);
if(mx[i]!=mx[i<<1|1]) cx[i]=max(cx[i],mx[i<<1|1]);
}
void ins(int i,int l,int r,int L,int R,int c)
{
if(L>r || l>R || mx[i]<=c) return ;
if(L<=l && r<=R && cx[i]<c)
{
s[i]++;mx[i]=c;
return ;
}
int mid=(l+r)>>1;down(i);
ins(i<<1,l,mid,L,R,c);
ins(i<<1|1,mid+1,r,L,R,c);
up(i);
}
int ask(int i,int l,int r,int x)
{
if(l==r) return s[i];
int mid=(l+r)>>1;down(i);
if(mid>=x) return ask(i<<1,l,mid,x);
return ask(i<<1|1,mid+1,r,x);
}
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++) a[i].pb({0,read()});
for(int i=1;i<=m;i++)
{
int op=read(),x=read();
if(op==1) a[x].pb({i,read()});
else q[x].pb(i);
}
for(int i=1;i<=n;i++) a[i].pb({m,0});
memset(mx,0x3f,sizeof mx);
memset(cx,-0x3f,sizeof cx);
for(int i=n;i>=1;i--)
{
for(int j=0;j+1<a[i].size();j++)
ins(1,1,m,a[i][j].first+1,
a[i][j+1].first,a[i][j].second);
for(int x:q[i]) ans[x]=ask(1,1,m,x);
}
for(int i=1;i<=m;i++)
if(ans[i]) printf("%d\n",ans[i]);
}
通用测评号
题目描述
一共有 \(n\) 只鸽子,小 R 每秒会等概率选择一只没有吃撑的鸽子并给他一粒玉米。一只鸽子饱了当且仅当它吃了的玉米粒数量 \(\geq b\),一只鸽子吃撑了当且仅当它吃了的玉米粒数量 \(\geq a\)
小 R 想要你告诉他,当所有鸽子都吃饱时,期望有多少只鸽子吃撑了。
解法
考虑贡献法,计算每个鸽子吃撑的贡献。由于这 \(n\) 只鸽子是全等的,所以只需要计算其他鸽子吃饱时,第一只鸽子已经吃撑的概率,那么这个概率乘上 \(n\) 就是答案。
考虑切换主体,所求概率等价于第一只鸽子吃撑时,存在鸽子还没吃饱的概率。此时的要求时没有吃饱的鸽子个数 \(\geq 1\),可以钦定没有吃饱的鸽子个数为 \(i\),会带来 \((-1)^{i}\) 的容斥系数:
其中 \(p_m\) 表示鸽子总数为 \(m\),第 \(1\) 只鸽子已经吃撑了,其他 \(m-1\) 只鸽子还没有吃饱的概率。我们枚举喂的次数 \(i\),强制最后一次喂第 \(1\) 只鸽子,然后计算合法的序列方案数,每种方案对概率的贡献是 \(\frac{1}{m^i}\):
设 \(f(x)=(1+\frac{x}{1!}+\frac{x^2}{2!}...\frac{x^{b-1}}{(b-1)!})\),那么现在的主要问题是快速求解 \(f^{m-1}(x)\)
考虑到 \((e^x)'=e^x\),可以对 \(f^{m}(x)\) 这个 \(\tt EGF\) 求导来找递推式:
对两边都是取 \([x^{i-1}]\) 得到:
发现 \([x^{i-1}]f^m(x)\) 和 \([x^{i-1}]\frac{x^{b-1}}{(b-1)!}\cdot f^{m-1}(x))\) 都是已知信息,所以可以直接递推,时间复杂度 \(O(n^2\cdot b)\)
总结
快速求 \(\tt EGF\) 的幂次,求导推通项是重要套路。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 255;
const int N = M*M;
#define int long long
const int MOD = 998244353;
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,a,b,ans,f[M][N],g[M][N],inv[N],fac[N],finv[N];
void init()
{
inv[0]=inv[1]=fac[0]=finv[0]=1;
for(int i=2;i<N;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<N;i++) finv[i]=finv[i-1]*inv[i]%MOD;
for(int i=1;i<N;i++) fac[i]=fac[i-1]*i%MOD;
f[0][0]=1;g[0][b-1]=finv[b-1];
for(int i=1;i<=n;i++)
{
f[i][0]=1;g[i][b-1]=finv[b-1];
for(int j=1;j<=i*(b-1);j++)
{
f[i][j]=(f[i][j-1]-g[i-1][j-1]+MOD)%MOD;
f[i][j]=f[i][j]*i%MOD*inv[j]%MOD;
g[i][j+b-1]=f[i][j]*finv[b-1]%MOD;
}
}
for(int i=0;i<=n;i++)
{
memset(g[i],0,sizeof g[i]);
for(int j=0;j<=i*(b-1);j++)
g[i][j+a-1]=f[i][j]*finv[a-1]%MOD;
}
}
int C(int n,int m)
{
if(n<m || m<0) return 0;
return fac[n]*finv[m]%MOD*finv[n-m]%MOD;
}
int qkpow(int a,int b)
{
int r=1;
while(b>0)
{
if(b&1) r=r*a%MOD;
a=a*a%MOD;
b>>=1;
}
return r;
}
int get(int m)
{
int r=0,pw=qkpow(m,MOD-1-a);
for(int i=a;i<=a+(m-1)*(b-1);i++)
{
r=(r+pw*g[m-1][i-1]%MOD*fac[i-1])%MOD;
pw=pw*inv[m]%MOD;
}
return r;
}
signed main()
{
n=read();a=read();b=read();init();
for(int i=1;i<n;i++)
{
int t=C(n-1,i)*get(i+1)%MOD;
if(i&1) ans=(ans+t)%MOD;
else ans=(ans+MOD-t)%MOD;
}
printf("%lld\n",ans*n%MOD);
}