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\):
\(~~~~\) 然后套路莫反:
\(~~~~\) 然后丢个结论:
证明:
\(~~~~\) 考虑 \(n\) 的质因子指数都 \(\leq 2\),不然必然为 \(0\).
\(~~~~\) 若 \(n=p^2q (\gcd(p,q)=1)\),那么:
那么你整除分块或者直接枚举 \(\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 /
*/