• 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}\) 的容斥系数:

    \[ans=n\cdot \sum_{i=1}^{n-1}{n-1\choose i}\cdot (-1)^{i}\cdot p_{i+1} \]

    其中 \(p_m\) 表示鸽子总数为 \(m\),第 \(1\) 只鸽子已经吃撑了,其他 \(m-1\) 只鸽子还没有吃饱的概率。我们枚举喂的次数 \(i\),强制最后一次喂第 \(1\) 只鸽子,然后计算合法的序列方案数,每种方案对概率的贡献是 \(\frac{1}{m^i}\)

    \[p_m=\sum_i (i-1)!\cdot [x^{i-1}](\frac{x^{a-1}}{(a-1)!}\cdot (1+\frac{x}{1!}+\frac{x^2}{2!}...\frac{x^{b-1}}{(b-1)})^{m-1}) \]

    \(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\) 求导来找递推式:

    \[(f^m(x))'=m\cdot f^{m-1}(x)\cdot f'(x)=m\cdot (f^m(x)-\frac{x^{b-1}}{(b-1)!}\cdot f^{m-1}(x)) \]

    对两边都是取 \([x^{i-1}]\) 得到:

    \[i[x^i]f^m(x)=m\cdot ([x^{i-1}]f^m(x)-[x^{i-1}]\frac{x^{b-1}}{(b-1)!}\cdot f^{m-1}(x)) \]

    发现 \([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);
    }
    
  • 相关阅读:
    vmware
    win10激活方法:
    Golang 值类型结构体和指针类型的结构体实现接口的区别(方法集)
    Golang 的无缓冲通道和有缓冲通道
    Golang 嵌入类型以及公开的属性和方法的解释
    Golang 值接受者类型方法和指针接收者类型方法的区别
    Golang 的 goroutine 的 竞争解决方法 原子操作atomic(乐观锁)和互斥锁mutex(悲观锁)
    Golang 的 Goroutine
    centos安装supervisor
    centos7 yum 安装最新redis
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16376035.html
Copyright © 2020-2023  润新知