[省选前集训2021] 模拟赛5

总结

尽力了,感觉确实有很多知识盲点。

\(\tt T3\) 乱贪心可以过掉但是我因为绑点没敢打,下次不管什么我都要乱贪心,管他能不能得分。

还有题一定要仔细看,怎么第一题题又读错了啊

rng

题目描述

有一个长度为 \(n\) 的序列 \(a_1,a_2...a_n\)\(a_i\) 为在 \([l_i,r_i]\) 中独立均匀随机生成的实数

求生成数列的期望逆序对个数模 \(998244353\) 的值。

\(n\leq 10^5,l_i<r_i\)

对于 \(20\%\) 的数据:\(l_i=0\)

解法

套路地考虑 \(i<j\) 的时候 \(a[i]>a[j]\) 的概率求和即可。

因为是取 \([l_i,r_i]\) 中的一个实数所以我不会了,其实这种问题主要转化成面积考虑即可。

首先考虑一下部分分 \(l_i=0\) 吧,其实就是在一个 \(r_i\times r_j\) 的矩阵里面选点,要求选到的点横坐标大于纵坐标即可,如下图:

就像上图一样,算面积在总面积里面占的比重就可以了,设 \(f(r_1,r_2)\) 表示对应的面积的两倍,那么分两种情况讨论一下。当 \(r_1\leq r_2\) 时,\(f(r_1,r_2)=r_1^2\),当 \(r_1>r_2\)\(f(r_1,r_2)=2r_1r_2-r_2^2\),概率是 \(\frac{f(r_1,r_2)}{r_1r_2}\)

接下来考虑 \(l_i\not=0\) 的情况,不难发现拿面积减一减就出来了,可以看下图:

所以核心的柿子是下面这样的:

\[\frac{f(r_1,r_2)-f(r_1,l_2)-f(l_1,r_2)+f(l_1,l_2)}{2(r_1-l_1)(r_2-l_2)} \]

然后拿六个树状数组维护一下 \(1,x,x^2\) 这三个值就可以快速求 \(f\) 了,写起来其实很方便的,注意中间不要乘爆了,时间复杂度 \(O(n\log n)\)感觉我的代码写的好整齐啊

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 200005;
const int MOD = 998244353;
const int inv2 = (MOD+1)/2;
#define int long long
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,a[M],l[M],r[M],b[M][6];
//0-2维护1/x/x^2(l),3-5维护1/x/x^2(r)
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 lowbit(int x)
{
	return x&(-x);
}
void add(int x,int f,int t)
{
	for(int i=x;i<=m;i+=lowbit(i))
		b[i][f]=(b[i][f]+t)%MOD;
}
int ask(int x,int f)
{
	int res=0;
	for(int i=x;i>0;i-=lowbit(i))
		res=(res+b[i][f])%MOD;
	return res;
}
int fuck(int x,int y)
{
	int t1=ask(m,y+1)-ask(x,y+1);
	int t=ask(m,y)-ask(x,y),res=0;
	res=ask(x,y+2);//r1<=r2
	res=(res+2*t1*a[x])%MOD;//r1>r2 
	res=(res-a[x]*a[x]%MOD*t)%MOD;//r1>r2
	return res*inv2%MOD;
}
signed main()
{
	freopen("rng.in","r",stdin);
	freopen("rng.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)
	{
		l[i]=read();r[i]=read();
		a[++m]=l[i];
		a[++m]=r[i];
	}
	sort(a+1,a+m+1);
	m=unique(a+1,a+m+1)-a-1;
	for(int i=1;i<=n;i++)
	{
		int inv=qkpow(r[i]-l[i],MOD-2);
		l[i]=lower_bound(a+1,a+m+1,l[i])-a;
		r[i]=lower_bound(a+1,a+m+1,r[i])-a;
		//下面是查询 
		ans=(ans+fuck(r[i],3)*inv)%MOD;
		ans=(ans-fuck(r[i],0)*inv)%MOD;
		ans=(ans-fuck(l[i],3)*inv)%MOD;
		ans=(ans+fuck(l[i],0)*inv)%MOD;
		//下面是修改
		add(l[i],0,inv);
		add(l[i],1,a[l[i]]*inv%MOD);
		add(l[i],2,a[l[i]]*a[l[i]]%MOD*inv%MOD);
		add(r[i],3,inv);
		add(r[i],4,a[r[i]]*inv%MOD);
		add(r[i],5,a[r[i]]*a[r[i]]%MOD*inv%MOD);
	}
	printf("%lld\n",(ans+MOD)%MOD);
}

lg

题目描述

给定 \(n,m\),求下列柿子:

\[\prod_{x_1,x_2...x_n\in[1,m]}lcm(x_1,x_2...x_n)^{\gcd(x_1,x_2...x_n)} \]

\(n\leq 10^8,m\leq 200000\)

解法

注意 \(lcm\not=\frac{mul}{\gcd}\) 啊,这个只在两个数的情况下成立,还有就是反演的时候带着 \(lcm\) 走下去是没问题的

看见 \(\tt gcd\) 就直接莫比乌斯反演吧:

\[\begin{aligned} &=\prod_{d=1}^m\prod_{x_i}lcm(x_i)^{d[\gcd(x_i)=d]}\\ &=\prod_{d=1}^m\prod_{d|x_i}(d\cdot lcm(x_i))^{d\sum_{k|\gcd(x_i)}\mu(k)}\\ &=\prod_{d=1}^m\prod_{k=1}^{m/d}\prod_{x_i=1}^{m/dk}(dk\cdot lcm(x_i))^{d\mu(k)}\\ &=\prod_{T=1}^m\prod_{x_i=1}^{m/T}(T\cdot lcm(x_i))^{\sum_{d|T}d\mu(\frac{T}{d})}\\ &=\prod_{T=1}^m\prod_{x_i=1}^{m/T}(T\cdot lcm(x_i))^{\varphi(T)} \end{aligned} \]

先推到这里把,然后你发现问题变成了选一个值域在 \([1,V=\frac{m}{T}]\) 中的序列,求所有情况的 \(lcm\) 的乘积,发现这个完全可以分质数考虑,对于质数 \(p\) 我们枚举 \(lcm\) 中它的次数 \(t\),那么选出来的序列至少要有数包含 \(p^t\) 这个质因子并且所有数 \(p\) 的指数都小于等于 \(t\),这个东西可以简单的容斥并且运用乘法原理知道方案数是这东西:

\[(F(p^0)+....F(p^t))^n-(F(p^0)+...F(p^{t-1}))^n \]

上面的 \(F(p^i)\) 表示是 \(p^i\) 的倍数但不是 \(p^{i+1}\) 的倍数。

然后就可以算了,现在来说明一下复杂度,\(p,t\) 枚举的复杂度是 \(O(V)\) 的因为枚举出来的 \(p^t\) 两两不同,那么总的时间复杂度就是调和级数求和,由于还要做快速幂,所以总时间复杂度 \(O(m\log m\log n)\)

注意 \(T\) 的指数是 \((\frac{m}{T})^{n}\),因为有那么多种 \(a\) 的排列,还是就是注意指数取模 \(998244353-1\)

#include <cstdio>
const int MOD = 998244353;
const int mod = MOD-1;
const int M = 200005;
#define int long long
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,cnt,ans=1,p[M],phi[M];
void init(int n)
{
	phi[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!phi[i])
		{
			phi[i]=i-1;
			p[++cnt]=i;
		}
		for(int j=1;j<=cnt && i*p[j]<=n;j++)
		{
			if(i%p[j]==0)
			{
				phi[i*p[j]]=phi[i]*p[j];
				break;
			}
			phi[i*p[j]]=phi[i]*phi[p[j]];
		}
	}
}
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 fast(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 lcm(int V)
{
	int res=1;
	for(int i=1;i<=cnt && p[i]<=V;i++)
	{
		for(int pt=1;pt<=V;pt*=p[i])
		{
			int tmp=fast(V-V/(p[i]*pt),n)-fast(V-V/pt,n);
			//其实就表示两个后缀相减 
			tmp=(tmp+mod)%mod; 
			res=res*qkpow(pt,tmp)%MOD;
		}
	}
	return res;
}
signed main()
{
	freopen("lg.in","r",stdin);
	freopen("lg.out","w",stdout);
	n=read();m=read();
	init(m);
	for(int i=1;i<=m;i++)
	{
		int tmp=qkpow(i,fast(m/i,n));
		ans=(ans*qkpow(tmp*lcm(m/i)%MOD,phi[i]))%MOD; 
	}
	printf("%lld\n",ans);
}

pm

题目描述

有一个的排列 \(\{a_i\}\),你需要把他变成 \(\{1,2...n\}\)

第一阶段是交换两个相邻的位置,随时可以选择结束第一阶段,第二阶段是任意修改任意一个位置上的值。

最小化总操作步数,输出第一阶段的操作即可。

\(n\leq 2\cdot 10^5\)

解法

定义段表示每个元素都必须参与交换的一个区间,且交换次数至少是 \(len-1\),那么问题变成了把原序列划分成若干个段 \([l,r]\),使得段内元素集合是 \([l,r]\),并且逆序对个数是 \(r-l\)(交换次数)

考虑 \(dp\),设 \(dp[r]\) 表示划分到 \(r\) 的最小步数,但是 \(r\) 可能有多个对应的 \(l\) 满足条件,我们选取哪一个呢?选取最近的那一个即可,这是由于如果选取了更长的段,我们显然可以用这个段将它分开。

那么问题变成了快速找到第一个满足条件的左端点,首先考虑元素集合是 \([l,r]\) 怎么判断,首先里面的元素要小于等于 \(r\),然后满足 \(\sum_{i=l}^r(a_i-i)=0\),所以做出 \(a_i-i\) 的前缀和即可,找到左边第一个相等的位置。

接下来就检查 \([l,r]\) 的逆序对是否是 \(r-l\),因为它的元素集合 \([l,r]\),所以用 \([1,r]\) 的逆序对减去 \([1,l-1]\) 的逆序对,减去 \([1,l-1]\) 中大于 \(r\) 的数 \(\times(r-l+1)\) 即可,可以用可持久化线段树维护,时间复杂度 \(O(n\log n)\)


上面的方法是垃圾,现在来讲正解。

正着扫一遍,如果能换就换,如果你觉得不保险再倒着扫一遍,时间复杂度 \(O(n)\)

但是正着扫一遍就过了

#include <cstdio>
#include <map>
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,a[M],ans[M];
signed main()
{
	freopen("pm.in","r",stdin);
	freopen("pm.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=1;i<=n;i++)
		if(a[i]==i+1 || a[i+1]==i)
		{
			ans[++m]=i;
			swap(a[i],a[i+1]);
		}
	printf("%d\n",m);
	for(int i=1;i<=m;i++)
		printf("%d\n",ans[i]);
}
posted @ 2021-03-21 19:58  C202044zxy  阅读(59)  评论(0编辑  收藏  举报