总结
尽力了,感觉确实有很多知识盲点。
( t T3) 乱贪心可以过掉但是我因为绑点没敢打,下次不管什么我都要乱贪心,管他能不能得分。
还有题一定要仔细看,怎么第一题题又读错了啊。
rng
题目描述
有一个长度为 (n) 的序列 (a_1,a_2...a_n),(a_i) 为在 ([l_i,r_i]) 中独立均匀随机生成的实数。
求生成数列的期望逆序对个数模 (998244353) 的值。
(nleq 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 imes r_j) 的矩阵里面选点,要求选到的点横坐标大于纵坐标即可,如下图:
就像上图一样,算面积在总面积里面占的比重就可以了,设 (f(r_1,r_2)) 表示对应的面积的两倍,那么分两种情况讨论一下。当 (r_1leq 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 ot=0) 的情况,不难发现拿面积减一减就出来了,可以看下图:
所以核心的柿子是下面这样的:
然后拿六个树状数组维护一下 (1,x,x^2) 这三个值就可以快速求 (f) 了,写起来其实很方便的,注意中间不要乘爆了,时间复杂度 (O(nlog 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
",(ans+MOD)%MOD);
}
lg
题目描述
给定 (n,m),求下列柿子:
(nleq 10^8,mleq 200000)
解法
注意 (lcm ot=frac{mul}{gcd}) 啊,这个只在两个数的情况下成立,还有就是反演的时候带着 (lcm) 走下去是没问题的
看见 ( t gcd) 就直接莫比乌斯反演吧:
先推到这里把,然后你发现问题变成了选一个值域在 ([1,V=frac{m}{T}]) 中的序列,求所有情况的 (lcm) 的乘积,发现这个完全可以分质数考虑,对于质数 (p) 我们枚举 (lcm) 中它的次数 (t),那么选出来的序列至少要有数包含 (p^t) 这个质因子并且所有数 (p) 的指数都小于等于 (t),这个东西可以简单的容斥并且运用乘法原理知道方案数是这东西:
上面的 (F(p^i)) 表示是 (p^i) 的倍数但不是 (p^{i+1}) 的倍数。
然后就可以算了,现在来说明一下复杂度,(p,t) 枚举的复杂度是 (O(V)) 的因为枚举出来的 (p^t) 两两不同,那么总的时间复杂度就是调和级数求和,由于还要做快速幂,所以总时间复杂度 (O(mlog mlog 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
",ans);
}
pm
题目描述
有一个的排列 ({a_i}),你需要把他变成 ({1,2...n})
第一阶段是交换两个相邻的位置,随时可以选择结束第一阶段,第二阶段是任意修改任意一个位置上的值。
最小化总操作步数,输出第一阶段的操作即可。
(nleq 2cdot 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) 的数 ( imes(r-l+1)) 即可,可以用可持久化线段树维护,时间复杂度 (O(nlog 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
",m);
for(int i=1;i<=m;i++)
printf("%d
",ans[i]);
}