2023.3.15 日寄

2023.3.15 日寄

杂题专训

Mergesort Strikes Back

题意

    [1,n] 为归并的第 1 层,求对一个排列归并排序只执行到第 k 层,操作完成后逆序对个数的期望。对一个大质数取模。
     1n,k105.

题解

     非常厉害的题目啊。(指很多性质

     首先是 合并排序(指没有进行递归)时候的结论:按照前缀最大值相同的数分为一块(如果归并有分块那以归并的分块来),那么此时排序就是将每个块的第一位从小到大排序。这个是 abracadabra 的套路。

     然后考虑对于每个初始的长为 L 的块,显然其内部的顺序不会再变,所以内部的数对 L(L1)2 个都有 12 的概率贡献,也就是 L(L1)4 的期望贡献。

     对于两个长度分别为 L1L2 的块,我们考虑其贡献的数对任何一个位置都必然不可能来自首位(首位是已经排过序的,并且由于首位是前缀最大值,不可能后面的位置有机会) 。那么也就是 L1+L22L1+L2 的概率找到数对,然后还是 12 的概率贡献。把这个式子拆开前缀和维护一下就好了。

代码
查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
int n,k,MOD;
inline int Add(int a,int b){return (a+b)%MOD;}
inline int Dec(int a,int b){return (a-b+MOD)%MOD;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
inline int qpow(int a,int b)
{
	int ret=1;
	while(b)
	{
		if(b&1) ret=Mul(ret,a);
		b>>=1;a=Mul(a,a);
	}
	return ret;
}
int buc[200005];
void Merge(int l,int r,int dep)
{
	if(dep==k||l==r){buc[r-l+1]++;return;}
	int mid=(l+r)>>1;
	Merge(l,mid,dep+1); Merge(mid+1,r,dep+1);
}
vector <int> Len;
int Pre[200005],Inv2,Inv4;
inline ll Calc(int x,int y)
{
	ll ret=Mul(Mul(x,y),Inv2);
	for(int i=1;i<=x;i++) ret=Dec(ret,Dec(Pre[i+y],Pre[i]));
	return ret;
}
int main() {
	read(n);read(k);read(MOD);
	Inv2=qpow(2,MOD-2); Inv4=qpow(4,MOD-2);
	for(int i=1;i<=100000;i++) Pre[i]=Add(Pre[i-1],qpow(i,MOD-2));
	Merge(1,n,1);
	for(int i=1;i<=n;i++) if(buc[i]) Len.push_back(i);
	ll Ans=0;
	for(int i=0;i<(int)Len.size();i++)
	{
		int L=Len[i];
		Ans=Add(Ans,Mul(buc[L],Mul(Mul(L,L-1),Inv4)));
		Ans=Add(Ans,Mul(Mul(Mul(buc[L],buc[L]-1),Inv2),Calc(L,L)));
	}
	for(int i=0;i<(int)Len.size();i++)
	{
		int L1=Len[i];
		for(int j=i+1;j<(int)Len.size();j++)
		{
			int L2=Len[j];
			Ans=Add(Ans,Mul(Mul(buc[L1],buc[L2]),Calc(L1,L2)));
		}
	}
	printf("%lld",Ans);
	return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
*/

「LibreOJ β Round #4」求和

题意

     给定正整数 n,m,求 i=1nj=1mμ2(gcd(i,j))998244353 取模的结果.
     1n,m1012.

题解

     套路性地先枚举 gcd

dμ2(d)i=1nj=1m[gcd(i,j)=d]

     然后套路莫反:

dmin(n,m)μ2(d)ndmdt|dμ(dt)

     然后丢个结论:

d|nμ2(d)×μ(nd)=μ(n)[nN]

证明:

     考虑 n 的质因子指数都 2,不然必然为 0.

    n=p2q(gcd(p,q)=1),那么:

d|nμ2(d)μ(nd)=d|qμ2dpμ(pqd)=d|qμ2(d)μ2(p)μ(qd)μ(p)=μ(p)d|qμ(d)=μ(p)[q=1]=μ(n)[nN]

那么你整除分块或者直接枚举 d 的值都是 O(n) 的做法,做就完了。这个trick可以记下来。

代码
查看代码
#include <bits/stdc++.h>
#define ll long long 
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
int Prime[4000005],tot;
int mu[4000005],vis[4000005];
void Init(int N)
{
	mu[1]=1;
	for(int i=2;i<=N;i++)
	{
		if(!vis[i]) {mu[i]=-1;Prime[++tot]=i;}
		for(int j=1;j<=tot&&i*Prime[j]<=N;j++)
		{
			vis[i*Prime[j]]=1;
			if(i%Prime[j]) mu[i*Prime[j]]=-mu[i];
			else break;
		}
	}
	for(int i=1;i<=N;i++) mu[i]+=mu[i-1];
}
const ll MOD=998244353;
inline ll Add(ll a,ll b){a%=MOD;b%=MOD;a+=b;return a>=MOD?a-MOD:a;}
inline ll Dec(ll a,ll b){a%=MOD;b%=MOD;a-=b;return a<0?a+MOD:a;}
inline ll Mul(ll a,ll b){a%=MOD;b%=MOD;return 1ll*a*b%MOD;}
inline ll qpow(ll a,ll b)
{
	ll ret=1;
	while(b)
	{
		if(b&1) ret=Mul(ret,a);
		b>>=1;a=Mul(a,a);
	}
	return ret;
}
int main() {
	Init(4000000);
	ll n,m,Ans=0;read(n);read(m);
	for(ll l=1,r;l<=n&&l<=m;l=r+1)
	{
		r=min(n/(n/l),m/(m/l));
		ll L=ceil(sqrt(l)),R=floor(sqrt(r));
		Ans=Add(Ans,Mul(Mul(n/l,m/l),Dec(mu[R],mu[L-1])));
	}
	printf("%lld",Ans);
	return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
*/

「AGC018C」 Coins

题意

    n=x+y+z 个人,每个人有三个属性 ai,bi,ci,要求选择 x 个人属性为 aiy 个人属性为 biz 个人属性为 ci,最终他们的和最大。求这个最大的和。
     1n105,1ai,bi,ci109.

题解

     有一个显然的费用流建模,看看数据范围,果断模拟费用流开始反贪。

     考虑先得到一组解,不妨就令前 x 个人取 a,中间 y 个人取 b,最后 z 个人取 c.那么我们考虑每次替换找新的方案。记 xy 表示把一个本来取 a 的变为 b。以此类推,那我们就有以下五种交换方案:

  • xy,yx
  • xz,zx
  • yz,zx
  • xy,yz,zx
  • xz,zy,yx

     每种都选那个交换过后能获取最大 Δ 的人,选择这五种当中最大的那一个反悔贪心即可。显然任何时候得到的解都是合法的。

代码
查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<ll,ll>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
ll Ans;
int Type[100005];
int a[100005][3];
struct cmp {
	bool operator()(const PII x,const PII y){return x.first!=y.first?x.first<y.first:x.second<y.second;}
}; 
priority_queue<PII,vector<PII>,cmp>Q[10];
void Add(int i)
{
	for(int j=0;j<=8;j++)
	{
		int x=j/3,y=j%3;
		if(x==y) continue;
		Q[j].push(mp(a[i][x]-a[i][y],i));
	}
}
int main() {
	int x,y,z,n;read(x);read(y);read(z); n=x+y+z;
	for(int i=1;i<=n;i++) read(a[i][0]),read(a[i][1]),read(a[i][2]),Add(i);
	for(int i=1;i<=x;i++) Ans+=a[i][0],Type[i]=0;
	for(int i=x+1;i<=x+y;i++) Ans+=a[i][1],Type[i]=1;
	for(int i=x+y+1;i<=n;i++) Ans+=a[i][2],Type[i]=2;

	while("I am an idiot.")
	{
		for(int i=0;i<=8;i++)
		{
			int X=i/3,Y=i%3;	
			if(X==Y) continue;
			while(!Q[i].empty()&&Type[Q[i].top().second]!=Y) Q[i].pop();
		}
		ll Val[9];
		for(int i=0;i<=8;i++)
		{
			if(!Q[i].empty()) Val[i]=Q[i].top().first;
			else Val[i]=-1e17;
		}
		ll Maxn=0,pos;
		/*五个环*/
		if(Val[3]+Val[7]+Val[2]>Maxn) Maxn=Val[3]+Val[7]+Val[2],pos=1;
		if(Val[6]+Val[5]+Val[1]>Maxn) Maxn=Val[6]+Val[5]+Val[1],pos=2;
		if(Val[3]+Val[1]>Maxn) Maxn=Val[3]+Val[1],pos=3;
		if(Val[6]+Val[2]>Maxn) Maxn=Val[6]+Val[2],pos=4;
		if(Val[7]+Val[5]>Maxn) Maxn=Val[7]+Val[5],pos=5;
		if(Maxn==0) break; Ans+=Maxn;
		int x,y,z;
		if(pos==1) Type[x=Q[3].top().second]=1,Type[y=Q[7].top().second]=2,Type[z=Q[2].top().second]=0,Add(x),Add(y),Add(z);
		if(pos==2) Type[x=Q[6].top().second]=2,Type[y=Q[5].top().second]=1,Type[z=Q[1].top().second]=0,Add(x),Add(y),Add(z);
		if(pos==3) Type[x=Q[3].top().second]=1,Type[y=Q[1].top().second]=0,Add(x),Add(y);
		if(pos==4) Type[x=Q[6].top().second]=2,Type[y=Q[2].top().second]=0,Add(x),Add(y);
		if(pos==5) Type[x=Q[7].top().second]=2,Type[y=Q[5].top().second]=1,Add(x),Add(y);
	}
	printf("%lld",Ans);
	return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。

 From  0  1  2
To
0      /  1  2
1      3  /  5
2      6  7  /
*/
posted @   Azazеl  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示