2023.3.15 日寄

2023.3.15 日寄

杂题专训

Mergesort Strikes Back

题意

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

题解

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

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

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

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

代码
查看代码
#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\),求 \(\sum_{i=1}^n \sum_{j=1}^m \mu^2(\gcd(i,j))\)\(998244353\) 取模的结果.
\(~~~~\) \(1\leq n,m\leq 10^{12}\).

题解

\(~~~~\) 套路性地先枚举 \(\gcd\)

\[\sum_{d} \mu^2(d) \sum_{i=1}^n \sum_{j=1}^m [\gcd(i,j)=d] \]

\(~~~~\) 然后套路莫反:

\[\sum_{d}^{\min(n,m)} \mu^2(d) \lfloor \frac{n}{d} \rfloor \lfloor \frac{m}{d} \rfloor \sum_{t|d} \mu(\frac{d}{t}) \]

\(~~~~\) 然后丢个结论:

\[\sum_{d|n} \mu^2(d) \times \mu(\frac{n}{d}) =\mu(\sqrt n) [\sqrt n \in \mathbb{N}] \]

证明:

\(~~~~\) 考虑 \(n\) 的质因子指数都 \(\leq 2\),不然必然为 \(0\).

\(~~~~\)\(n=p^2q (\gcd(p,q)=1)\),那么:

\[\sum_{d|n} \mu^2(d) \mu(\frac{n}{d})=\sum_{d|q} \mu^2{dp}\mu(\frac{pq}{d})\\ =\sum_{d|q} \mu^2(d) \mu^2(p)\mu(\frac{q}{d})\mu(p)\\ =\mu(p)\sum_{d|q}\mu(d)\\ =\mu(p)[q=1]\\ =\mu(\sqrt{n})[\sqrt{n}\in \mathbb{N}] \]

那么你整除分块或者直接枚举 \(\sqrt{d}\) 的值都是 \(\mathcal{O(\sqrt{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\) 个人,每个人有三个属性 \(a_i,b_i,c_i\),要求选择 \(x\) 个人属性为 \(a_i\)\(y\) 个人属性为 \(b_i\)\(z\) 个人属性为 \(c_i\),最终他们的和最大。求这个最大的和。
\(~~~~\) \(1\leq n\leq 10^5,1\leq a_i,b_i,c_i\leq 10^9\).

题解

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

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

  • \(x \rightarrow y,y\rightarrow x\)
  • \(x \rightarrow z,z\rightarrow x\)
  • \(y \rightarrow z,z\rightarrow x\)
  • \(x \rightarrow y,y\rightarrow z,z\rightarrow x\)
  • \(x \rightarrow z,z\rightarrow y,y\rightarrow x\)

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

代码
查看代码
#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 @ 2023-03-15 22:07  Azazеl  阅读(34)  评论(0编辑  收藏  举报