【超好懂的比赛题解】暨南大学2023东软教育杯ACM校赛个人题解


title : 暨南大学2023东软教育杯ACM校赛 题解
tags : ACM,练习记录
date : 2023-3-26
author : Linno


在这里插入图片描述

文章目录

  • 暨南大学2023东软教育杯ACM校赛 题解
      • A-小王的魔法
      • B-苏神的遗憾
      • C-神父的碟
      • D-基站建设
      • E-小王的数字
      • F-Uziの真身
      • G-电子围棋
      • H-二分大法
      • I-丁真的小马朋友们
      • J-单车运营
      • K-超导铁轨
      • L-承太郎的"替身数"

暨南大学2023东软教育杯ACM校赛 题解

题目链接:https://ac.nowcoder.com/acm/contest/47948

出题数量:12/12 ,AK了

出题顺序:A->B->C->D->F->G->J->E->I->H->L->K

简评:首先感谢出题组手下留情,再来点中等水平思维题估计就能搞死我了。因为题出的都比较板所以顺理成章地AK了,打搅了大家的做题兴致非常抱歉!这一场如果板子带够了并且刷题量达到一定水平,打得都不会差的。如果觉得自己打得不好,那希望这篇题解能给你带来帮助(不懂也可以继续问),喜欢ACM的话就请继续加油吧!

A-小王的魔法

选某一个数,它的因数都会被选中,因此选它本身肯定是最优的,那么我们直观觉得从大到小枚举的时候把因数都打上标签,然后没打标签的数统计到答案里面就必然是正确的。但可惜n有1e18这样做肯定超时,因此我们继续考虑更直观的结论:

  • i < = ⌊ n 2 ⌋ i<=\lfloor\frac{n}{2}\rfloor i<=2n时,显然有 i i i的所有因数都包含在 2 ∗ i 2*i 2i的所有因数中,因此不必考虑
  • 对于 i ∈ [ n 2 , n ] i\in [\frac{n}{2},n] i[2n,n],没有倍数能够将其包含,所以是最优的,因此答案就是 ⌈ n 2 ⌉ \lceil\frac{n}{2}\rceil 2n
void solve(){
    int n;
    cin>>n;
    cout<<(n+1)/2<<"\n";
}

B-苏神的遗憾

注意读题,苏神是可以第一的,但是成绩不能并列。因此一般情况下苏神的成绩是第二名成绩-1秒,但是如果那是第一名的成绩,就要跑得比这个更快一秒才行了。

void solve(){
    cin>>n;
    for(int i=1;i<=n;++i) cin>>a[i];
    stable_sort(a+1,a+1+n);
    if(a[1]+1==a[2]) ans=a[1]-1;
    else ans=a[2]-1;
    cout<<ans<<"\n";
}

C-神父的碟

前置知识:中国剩余定理

原题意也就是让我们解同余方程组:
{ x ≡ b 1 m o d    a 1 x ≡ b 2 m o d    a 2 . . . x ≡ b n m o d    a n \begin{cases} x\equiv b_1 \mod a_1 \\ x\equiv b_2 \mod a_2 \\ ...\\ x\equiv b_n \mod a_n \\ \end{cases} xb1moda1xb2moda2...xbnmodan
由于 a a a两两互质,令 x = ( N / a i ) ∗ y x=(N/a_i)*y x=(N/ai)y,方程组等同于解同余方程 ( N / a i ) y ≡ 1 m o d    a i (N/a_i)y\equiv 1 \mod a_i (N/ai)y1modai,得到特解 y i y_i yi,则方程组的解为: x 0 = b 1 x 1 + b 2 x 2 + . . . + b r x r m o d    N x_0=b_1x_1+b_2x_2+...+b_rx_r \mod N x0=b1x1+b2x2+...+brxrmodN,在模N意义下唯一。
注意到准备的箱子数 a i a_i ai肯定是不同的质数,也就是说不需要用到扩展CRT,直接套板子即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20;
int n,a[N],b[N],m[N],t[N],M=1;

inline int exgcd(int a,int b,int &x,int &y){
	if(!b){
		x=1;y=0;
		return a;
	}
	int d=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return d;
}

inline int inv(int a,int b){
	int d,x,y;
	d=exgcd(a,b,x,y);
	return (x<0)?(x+b):x;
}

void solve(){
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>a[i]>>b[i];
		M*=a[i]; 
	}
	int ans=0;
	for(int i=1;i<=n;++i){
		m[i]=M/a[i];
		t[i]=inv(m[i],a[i]);
		ans+=b[i]*m[i]%M*t[i]%M;
		ans%=M; 
	}
	cout<<ans<<"\n";
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T=1;
	while(T--){
		solve(); 
	}
	return 0;
}

D-基站建设

题目给定 x , b x,b x,b,我们可以转化为每个节点的做右端点 [ l , r ] [l,r] [l,r],并且按照 r r r从小到大排序,每次贪心地选最右点建基站并跳过已被覆盖掉的节点,即可保证最优。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+7;

struct E{
	int l,r;
	bool operator <(const E &B)const{
		return r<B.r;
	} 
}s[N];

void solve(){
	int n;
	cin>>n;
	for(int i=1,x,b;i<=n;++i){
		cin>>x>>b;
		s[i].l=x-b;s[i].r=x+b;
	}
	stable_sort(s+1,s+1+n);
	int lst=-0x3f3f3f3f,ans=0;
	for(int i=1;i<=n;++i){
		if(lst<s[i].l){
			lst=s[i].r;
			++ans;
		}
	}
	cout<<ans<<"\n";
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T=1;
	while(T--){
		solve(); 
	}
	return 0;
}

E-小王的数字

前置知识:数位DP

数位DP板题,形式一般都如下: 给定 L , R , 问 [ L , R ] 范围内有多少 X X X 的数,数据范围可出到 1 e 18 给定L,R,问[L,R]范围内有多少XXX的数,数据范围可出到1e18 给定L,R,[L,R]范围内有多少XXX的数,数据范围可出到1e18

流程大概就是按位拆分给的数,然后再按位记忆化搜索统计答案。代码细节各位自己看吧,dfs(stp,zero,lim,mx,now)表示搜到第stp位数,最多连续mx位6,当前连续了now位6时的状态,zero是前置0标签,lim是最高位标签。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=66;

int len=0,num[N],vis[N][N][N];

int dfs(int stp,bool zero,bool lim,int mx,int now){
	if(!stp) return (mx>=3);
	if(!zero&&!lim&&vis[stp][mx][now]) return vis[stp][mx][now];
	int j=lim?num[stp]:9,ans=0;
	for(int i=0;i<=j;++i){
		int tmp=(i==6)?now+1:0;
		ans+=dfs(stp-1,zero&(i==0),lim&(i==num[stp]),max(mx,tmp),tmp);
	}
	if(!zero&&!lim) vis[stp][mx][now]=ans;
	return ans;
}

int solve(int x){
	len=0;
	memset(vis,0,sizeof(vis));
	while(x){
		num[++len]=x%10;
		x/=10;
	}
	return dfs(len,1,1,0,0);
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int L,R;
	cin>>L>>R;
	cout<<solve(R)-solve(L-1)<<"\n";
	return 0;
}
/*
1 1000000000000000000
*/ 

F-Uziの真身

看大家的代码写的都好短,我开数组记了前缀和和后缀和。然后枚举‘z’的位置,每个位置对答案的贡献就是前面‘j’的数量乘上后面‘h’的数量,记得取模。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
const int N=2e6+7;
int len;
string str;

int numj[N],numh[N];

void solve(){
	cin>>len;
	cin>>str;
	int ans=0;
	numj[0]=(str[0]=='j');numh[len]=0;
	for(int i=1;i<len;++i){
		numj[i]=numj[i-1]+(str[i]=='j');
	}
	for(int i=len-1;i>=0;--i){
		numh[i]=numh[i+1]+(str[i]=='h');
	}
//	for(int i=0;i<len;++i) cout<<numj[i]<<" "<<numh[i]<<" !!\n";
	for(int i=1;i<len;++i){
		if(str[i]=='z') ans+=numj[i-1]*numh[i+1]%mod,ans%=mod;
//		cout<<i<<" "<<ans<<" !!\n"; 
	}
	cout<<ans<<"\n";
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T=1;
	while(T--){
		solve(); 
	}
	return 0;
}

/*
14
mjbajzhmuaxing
9
jzhjzhjzh
*/

G-电子围棋

爆搜就行了,首先把最外面一圈全变成1,然后剩下里面的就直接全变成6,最后统计答案即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=55;

int n,vis[N][N],mp[N][N];
int xx[]={0,0,-1,1},yy[]={-1,1,0,0};

bool check(int x,int y){return (x>=1&&x<=n&&y>=1&&y<=n&&!vis[x][y]&&mp[x][y]==0);}

inline void dfs(int x,int y,int id){
	mp[x][y]=id;vis[x][y]=1;
	for(int d=0;d<4;++d){
		int nx=x+xx[d],ny=y+yy[d];
		if(check(nx,ny)) dfs(nx,ny,id);
	}
}

void solve(){
	cin>>n;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j) cin>>mp[i][j];
	}
	for(int i=1;i<=n;++i){
		if(!vis[i][1]&&mp[i][1]==0) dfs(i,1,1);
		if(!vis[i][n]&&mp[i][n]==0) dfs(i,n,1);
		if(!vis[n][i]&&mp[n][i]==0) dfs(n,i,1);	
		if(!vis[1][i]&&mp[1][i]==0) dfs(1,i,1);
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			if(!vis[i][j]&&mp[i][j]==0) dfs(i,j,6);
		}
	}
	int ans=0;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			if(mp[i][j]==6) ++ans; 
		}
	}
	cout<<ans<<"\n";
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T=1;
	while(T--){
		solve(); 
	}
	return 0;
}

H-二分大法

没想到这题居然是暴力……大家可以忽略我的做法去看别人的。题目如果不保证所有i累加小于1e7的话,可以考虑建平衡树,我用的是FHQ Treap。这种数据结构可以很方便地进行序列的拆分和合并操作。然后对于拆分之后的两颗平衡树每个节点都有加法标签和乘法标签,仿照线段树的pushdown操作进行先乘后加的处理即可。可能场内没有想我一样写平衡树的同学吧……pushdown真的很坑,让我wa了两发。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+7;
const int mod=1000000007;

inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=f*-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return x*f;
}

int n,q,a[N];
int ch[N][2],val[N],pri[N],sz[N],tg1[N],tg2[N],cnt,rt;

void update(int x){
	sz[x]=1+sz[ch[x][0]]+sz[ch[x][1]];
}
#define mid ((l+r)>>1)
#define ls (ch[x][0])
#define rs (ch[x][1])
void pushdown(int x){
	if(x&&tg2[x]!=1){
		if(ls){
			val[ls]=val[ls]*tg2[x]%mod;
			tg1[ls]=tg1[ls]*tg2[x]%mod;
			tg2[ls]=tg2[ls]*tg2[x]%mod;	
		}
		if(rs){
			val[rs]=val[rs]*tg2[x]%mod;
			tg1[rs]=tg1[rs]*tg2[x]%mod;
			tg2[rs]=tg2[rs]*tg2[x]%mod;	
		}
		tg2[x]=1;
	}
	if(x&&tg1[x]){
		if(ls) val[ls]=(val[ls]+tg1[x])%mod,tg1[ls]=(tg1[ls]+tg1[x])%mod;
		if(rs) val[rs]=(val[rs]+tg1[x])%mod,tg1[rs]=(tg1[rs]+tg1[x])%mod;
		tg1[x]=0;
	}
}

int new_node(int v){
	sz[++cnt]=1;
	tg1[cnt]=0;tg2[cnt]=1;
	val[cnt]=v;
	pri[cnt]=rand();
	return cnt;
}

int merge(int x,int y){
	if(!x||!y) return x+y;
	pushdown(x);pushdown(y);
	if(pri[x]<pri[y]){
		ch[x][1]=merge(ch[x][1],y);
		update(x);
		return x;
	}else{
		ch[y][0]=merge(x,ch[y][0]);
		update(y);
		return y;
	}
}

void split(int now,int k,int &x,int &y){
	if(!now) x=y=0;
	else{
		pushdown(now);
		if(k<=sz[ch[now][0]]) y=now,split(ch[now][0],k,x,ch[now][0]);
		else x=now,split(ch[now][1],k-sz[ch[now][0]]-1,ch[now][1],y);
		update(now);
	}
}

int build(int l,int r){
	if(l>r) return 0;
	int now=new_node(a[mid]);
//	cout<<l<<" "<<r<<" "<<a[mid]<<" !!\n"; 
	ch[now][0]=build(l,mid-1);
	ch[now][1]=build(mid+1,r);
	update(now);
	return now;
}

void dfs(int x){
	if(!x) return;
	pushdown(x);
	dfs(ls);
	printf("%lld ",val[x]%mod);
	dfs(rs);
}

signed main(){	
	srand(time(0));
	n=read();
	for(int i=1;i<=n;++i) a[i]=read()%mod;
	rt=build(1,n);
	q=read();
	for(int i=1,x,op,y,a,b;i<=q;++i){
		x=read();op=read();y=read();
		split(rt,x,a,b);
		if(op==1) val[a]=(val[a]+y)%mod,tg1[a]=(tg1[a]+y)%mod;
		else val[a]=val[a]*y%mod,tg2[a]=tg2[a]*y%mod;
		rt=merge(b,a);
	}
	dfs(rt);
	puts("");
}

/*
7
1 2 3 4 5 6 7 
3
3 1 7
2 1 1
1 2 2


4
2 5 4 3
0

4
2 5 4 3
3
1 2 2
3 1 3
1 2 5
——8 7 6 20
 
3
0 5 4
3
2 2 5
2 2 3
3 1 7 

*/

I-丁真的小马朋友们

前置知识:线段树

首先要想到一个很直观的结论就是:我们每次都只选择长度为2的区间可以保证答案是最大的。因此我们只需要维护相邻两项的乘积即可。题目转化为了区间查询最大值和单点修改,学过数据结构的同学可以快速通过。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+7;

#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
int n,k,a[N],b[N],mx[N<<2];

void build(int p,int l,int r){
	if(l==r){
		mx[p]=b[l];
		return;
	}
	build(ls,l,mid);
	build(rs,mid+1,r);
	mx[p]=max(mx[ls],mx[rs]);
}

void update(int p,int l,int r,int ql,int qr,int k){
	if(ql<=l&&r<=qr){
		mx[p]=k;
		return;
	}
	if(ql<=mid) update(ls,l,mid,ql,qr,k);
	if(qr>mid) update(rs,mid+1,r,ql,qr,k);
	mx[p]=max(mx[ls],mx[rs]);
}

int query(int p,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr) return mx[p];
	int res=0;
	if(ql<=mid) res=query(ls,l,mid,ql,qr);
	if(qr>mid) res=max(res,query(rs,mid+1,r,ql,qr));
	return res;
}

void solve(){
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<n;++i) b[i]=a[i]*a[i+1];b[n]=b[n-1];
	build(1,1,n);
	cin>>k;
	for(int i=1,op,l,r;i<=k;++i){
		cin>>op>>l>>r;
		if(op==1){
			b[l]=b[l]/a[l]*r;
			if(l>1) b[l-1]=b[l-1]/a[l]*r; 
			if(l==n) b[n-1]=b[n]; 
			else if(l==n-1) b[n]=b[n-1];
			a[l]=r;
			update(1,1,n,n-1,n-1,b[n-1]); 
			update(1,1,n,n,n,b[n]);
			update(1,1,n,l,l,b[l]);
			if(l>1) update(1,1,n,l-1,l-1,b[l-1]);
		}else{
			cout<<query(1,1,n,l,r-1)<<"\n";
		}
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T=1;
	while(T--){
		solve(); 
	}
	return 0;
}
/*
5
1 2 3 4 5
4
2 1 2
2 1 3
2 4 5
2 3 5

5
1 2 5 3 4
4
2 1 2
2 1 3
2 4 5
2 3 5

6
2 8 9 1 11 3
6
2 1 5
2 1 3
1 1 10
2 1 2
2 2 3
2 1 3

6
2 8 9 1 11 3
7
2 1 5
2 1 3
1 6 10
2 1 6
2 5 6
2 4 5
2 4 6
*/

J-单车运营

前置知识:图论基础知识,最小费用最大流

最大流是在一个有向图中从S到T,中间有许多节点,每条边权限制了流量,问单位时间能流过的最大流量是多少的模型。而最小费用最大流流是在这基础上给出每条边每流过一单位水流的费用,问在最大流量的基础上最小费用的模型。显然题目可以套入这个模型,从源点S到每个地点之间的最大流量便是单车最开始的摆放量 a i a_i ai,不造成费用;每两个地点之间的流量无限,即可以不限量的搬运,但是需要造成的费用便是两地的距离 d i s i j dis_{ij} disij;每个地点到汇点的流量便是最终要摆放的车辆 b i b_i bi,也不造成费用。这样,在满足摆放好车辆的前提下(最大流),所需要的努力最少(最小费用)。模型是怎么做到这一点的?去学前置知识。

#include<bits/stdc++.h>
#define int long long
#define inf 0x7f7f7f7f
using namespace std;
const int N=305,M=2e5+7; 

inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=f*-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return x*f;
}

inline void write(int x){
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

int v[M],nxt[M],f[M],cnt=1;
int flow[N],w[M],head[N];

inline void addedge(int x,int y,int z,int k){
	++cnt;v[cnt]=y;w[cnt]=z;f[cnt]=k;nxt[cnt]=head[x];head[x]=cnt;
	++cnt;v[cnt]=x;w[cnt]=0;f[cnt]=-k;nxt[cnt]=head[y];head[y]=cnt;
}

int n,m,S,T;
int dis[N],mcost,mflow;
int inq[N],pre[N];
int mp[N][N],tot=0,q[M*10];

inline bool spfa(){
	for(int i=S;i<=T;++i) dis[i]=inf,inq[i]=0;
	int L=1,R=1;q[L]=S;
	inq[S]=1;dis[S]=0;flow[S]=inf;
	while(L<=R){
		int fro=q[L];++L;
		inq[fro]=0;
		for(int i=head[fro];i;i=nxt[i]){
			if(w[i]&&dis[v[i]]>dis[fro]+f[i]){
				dis[v[i]]=dis[fro]+f[i];
				flow[v[i]]=min(flow[fro],w[i]);
				pre[v[i]]=i;
				if(!inq[v[i]]){
					q[++R]=v[i];
					inq[v[i]]=1;
				}
			}
		} 
	}
	return dis[T]!=inf;
}

inline void update(){
	int x=T;
	while(x!=S){
		int i=pre[x];
		w[i]-=flow[T];
		w[i^1]+=flow[T];
		x=v[i^1];
	}
	mflow+=flow[T];
	mcost+=flow[T]*dis[T];
}

void EK(){
	while(spfa()) update();
}

void solve(){
	n=read();
	S=0;T=n+1; 	
	for(int i=1;i<=n;++i) addedge(S,i,read(),0);
	for(int i=1;i<=n;++i) addedge(i,T,read(),0);
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			mp[i][j]=read();
			addedge(i,j,inf,mp[i][j]);
		}
	}
	EK();
	write(mcost); 
}

signed main(){
	int T=1;
	while(T--){
		solve(); 
	}
	return 0;
}

K-超导铁轨

前置知识:SAM(后缀自动机)

因为被选中的位置不可用,所对于两个字符串S,T来说都可以分段处理。也就是说S的每一段与T的每一段两两匹配,求最长公共子串的最大长度。显然两两匹配会超时,我们不妨对S的每一段字符串建广义SAM,然后枚举T的每一段在SAM上跑最长匹配长度。别问我SAM是怎么做到的(其实就是在DAG上一个一个字符去匹配,如果匹配失败就像KMP一样跳转到Fail的位置),去学前置知识。听说大佬用后缀数组被卡了,可惜。

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+7;

int a[N],b[N],lst,tot=1,rt=1,num,ans=0;
int fa[N],len[N],ch[N][30],sz[N][12];
char s[N];
vector<int>G[N];
vector<string>s1,s2;
string str1,str2;

void insert(int c,int id){ 
	int p=lst,np=lst=++tot;
	len[np]=len[p]+1,sz[np][id]++;
	while(p&&!ch[p][c]) ch[p][c]=np,p=fa[p];
	if(!p) fa[np]=rt;
	else{
		int q=ch[p][c];
		if(len[q]==len[p]+1) fa[np]=q;
		else{
			int nq=++tot;
			memcpy(ch[nq],ch[q],sizeof(ch[q]));
			len[nq]=len[p]+1,fa[nq]=fa[q],fa[q]=fa[np]=nq;
			while(ch[p][c]==q) ch[p][c]=nq,p=fa[p];
		}
	}
}

void dfs(int x){
	int len=G[x].size();
	for(int i=0;i<len;++i){
		int y=G[x][i];
		dfs(y);
		for(int id=1;id<=num;++id) sz[x][id]+=sz[y][id];
	}
}

bool check(int p){ 
	if(!p) return false;
	for(int i=1;i<=num;++i){
		if(sz[p][i]) return true;
	}
	return false;
}

void work(string s){
	int p=rt,l=0;
	for(int i=0;i<s.length();++i){
		int c=s[i]-'a';
		if(check(ch[p][c])) l++,p=ch[p][c];
		else{
			while(p&&!check(ch[p][c])) p=fa[p];
			if(p) l=len[p]+1,p=ch[p][c];
			else l=0,p=rt;
		}
		ans=max(ans,l);
	}
}


signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>str1>>str2;
	int l1=str1.length(),l2=str2.length(),n,m;
	memset(a,-1,sizeof(a)); 
	memset(b,-1,sizeof(b));
	cin>>n;for(int i=1;i<=n;++i) cin>>a[i];
	cin>>m;for(int i=1;i<=m;++i) cin>>b[i];
	stable_sort(a+1,a+1+n);
	stable_sort(b+1,b+1+m);
//	
	string tmp="";
	for(int i=0,j=1;i<l1;++i){
		if(i==a[j]){
			if(tmp!="") s1.emplace_back(tmp);
			++j;tmp="";
		}else tmp.push_back(str1[i]);
	}
	if(tmp!="") s1.emplace_back(tmp);
	tmp="";
	for(int i=0,j=1;i<l2;++i){
		if(i==b[j]){
			if(tmp!="") s2.emplace_back(tmp);
			++j;tmp="";
		}else tmp.push_back(str2[i]);
	}
	if(tmp!="") s2.emplace_back(tmp);
//	
	for(auto s:s1){
		lst=rt;num++;
	//	cout<<s<<" !!\n";
		for(int i=0;i<s.length();++i) insert(s[i]-'a',num);
	}
	for(int i=2;i<=tot;++i) G[fa[i]].emplace_back(i);
	dfs(rt);
	for(auto s:s2){ 
		work(s);
	//	cout<<s<<" "<<ans<<" !!\n";
	}
	cout<<ans<<"\n";
	return 0;
} 

/*
aabaabaab
aa
3
3 6 9
0

abcdabcdbacbd
bcd
1
4
1
1

*/

L-承太郎的"替身数"

前置知识:数论分块,迪利克雷卷积,莫比乌斯反演

下面是推导过程,如果有不懂的私信我或者去看前置知识。

题意转化为求 ∑ i = 1 S 1 ∑ j = 1 S 2 l c m ( i , j ) \sum_{i=1}^{S1}\sum_{j=1}^{S2}lcm(i,j) i=1S1j=1S2lcm(i,j),最小公倍数 l c m ( i , j ) = i ∗ j g c d ( i , j ) lcm(i,j)=\frac{i*j}{gcd(i,j)} lcm(i,j)=gcd(i,j)ij
原式等于 ∑ i = 1 n ∑ j = 1 m i ⋅ j gcd ⁡ ( i , j ) = ∑ d = 1 n d ⋅ ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ [ gcd ⁡ ( i , j ) = 1 ]   i ⋅ j = ∑ d = 1 n ∑ d ∣ i n ∑ d ∣ j m μ ( d ) ⋅ i ⋅ j 令 g ( n , m ) = ∑ i = 1 n ∑ j = 1 m i ⋅ j = n ⋅ ( n + 1 ) 2 × m ⋅ ( m + 1 ) 2 sum ⁡ ( n , m ) = ∑ d = 1 n μ ( d ) ⋅ d 2 ⋅ g ( ⌊ n d ⌋ , ⌊ m d ⌋ ) 原式 = ∑ d = 1 n d ⋅ sum ⁡ ( ⌊ n d ⌋ , ⌊ m d ⌋ ) , 可用数论分块和线性筛解决。 原式等于 \sum_{i=1}^n\sum_{j=1}^m\frac{i\cdot j}{\gcd(i,j)} \\ = \sum_{d=1}^n d\cdot\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}[\gcd(i,j)=1]\ i\cdot j \\ = \sum_{d=1}^n\sum_{d\mid i}^n\sum_{d\mid j}^m\mu(d)\cdot i\cdot j\\ 令 g(n,m)=\sum_{i=1}^n\sum_{j=1}^m i\cdot j=\frac{n\cdot(n+1)}{2}\times\frac{m\cdot(m+1)}{2} \\ \operatorname{sum}(n,m)=\sum_{d=1}^n\mu(d)\cdot d^2\cdot g(\lfloor\frac{n}{d}\rfloor,\lfloor\frac{m}{d}\rfloor) \\ 原式=\sum_{d=1}^n d\cdot\operatorname{sum}(\lfloor\frac{n}{d}\rfloor,\lfloor\frac{m}{d}\rfloor) ,可用数论分块和线性筛解决。 原式等于i=1nj=1mgcd(i,j)ij=d=1ndi=1dnj=1dm[gcd(i,j)=1] ij=d=1ndindjmμ(d)ijg(n,m)=i=1nj=1mij=2n(n+1)×2m(m+1)sum(n,m)=d=1nμ(d)d2g(⌊dn,dm⌋)原式=d=1ndsum(⌊dn,dm⌋),可用数论分块和线性筛解决。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
const int N=1e7+7;

int np[N],pri[N],mu[N],sum[N],cnt=0;
void init(){
	mu[1]=np[1]=1;
	for(int i=2;i<N;++i){
		if(!np[i]) pri[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt&&pri[j]*i<N;++j){
			np[pri[j]*i]=1;
			if(i%pri[j]) mu[i*pri[j]]=-mu[i];
			else break;
		}
	}
	for(int i=1;i<N;++i) sum[i]=(sum[i-1]+i*i%mod*(mu[i]+mod)%mod)%mod;
} 

int Sum(int x,int y){
	return (x*(x+1)/2%mod)*(y*(y+1)/2%mod)%mod;
}

int func(int x,int y){
	int res=0;
	for(int l=1,r;l<=min(x,y);l=r+1){
		r=min(x/(x/l),y/(y/l));
		res=(res+(sum[r]-sum[l-1]+mod)*Sum(x/l,y/l)%mod)%mod;
	}
	return res;
}

int Solve(int x,int y){
	int res=0;
	for(int l=1,r;l<=min(x,y);l=r+1){
		r=min(x/(x/l),y/(y/l));
		res=(res+(r-l+1)*(l+r)/2%mod*func(x/l,y/l)%mod)%mod;
	}
	return res;
}

signed main(){
 	ios::sync_with_stdio(0);
 	cin.tie(0);cout.tie(0);
 	init();
	int t,n,m;
	cin>>t;
	while(t--){
		cin>>n>>m;
		cout<<Solve(n,m)<<"\n"; 
	}
	return 0;
}
/*
2
4 5
4 5
*/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/3798.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript实现列表分页(小白版)

组件用惯了&#xff0c;突然叫你用纯cssJavaScript写一个分页&#xff0c;顿时就慌了。久久没有接触js了&#xff0c;不知道咋写了。本文章也是借与参考做的一个demo案例&#xff0c;小白看了都会的那种。咱们就以ul列表为例进行分页&#xff1a; 首先模拟的数据列表是这样的&a…

变量的理论分布模型

二项分布 定义 对立事件的总体分布&#xff0c;称为二项分布。 例如&#xff0c;一个群体只有男和女&#xff0c;现在进行n次随机抽样调查&#xff0c;随机抽样男出现的次数可能是0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;…&#xff0c;n, 这种类…

网络安全实战从 0 到 1 彻底掌握 XXE

0x01 什么是 XXE个人认为&#xff0c;XXE 可以归结为一句话&#xff1a;构造恶意 DTD介绍 XXE 之前&#xff0c;我先来说一下普通的 XML 注入&#xff0c;这个的利用面比较狭窄&#xff0c;如果有的话应该也是逻辑漏洞。既然能插入 XML 代码&#xff0c;那我们肯定不能善罢甘休…

ROS Cartographer--Algorithm

ROS Cartographer–Algorithm 原文&#xff1a;Algorithm walkthrough for tuning 论文地址(Google Search)&#xff1a;Real-Time Loop Closure in 2D LIDAR SLAM ROS Cartographer的完整参考文件&#xff1a;Cartographer ROS Integration 概述 本地SLAM通常由前端和后端…

Python满屏表白代码

目录 前言 爱心界面 无限弹窗 前言 人生苦短&#xff0c;我用Python&#xff01;又是新的一周啦&#xff0c;本期博主给大家带来了一个全新的作品&#xff1a;满屏表白代码&#xff0c;无限弹窗版&#xff01;快快收藏起来送给她吧~ 爱心界面 def Heart(): roottk.Tk…

【Linux】计算机网络1

计算机网络的背景背景&#xff1a;早在20世纪50年代初&#xff0c;美国建立的地面防空系统就是将地面的雷达和其他测量控制设备的信息通过通信线路汇集到一台中心计算机进行处理&#xff0c;开创了把计算机技术和通信技术相结合的尝试。20世纪60年代中期开始&#xff0c;出现、…

OSPF----特殊区域

目录 OSPF----特殊区域 第一大类----末梢区域&#xff08;Stub Area&#xff09; 完全末梢区域&#xff08;(Totally Stub Area) 第二大类特殊区域----非完全末梢区域&#xff08;NSSA&#xff09; OSPF----特殊区域 第一大类----末梢区域&#xff08;Stub Area&#xff09…

动态版通讯录——“C”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰的内容是动态版通讯录啦&#xff0c;其实之前&#xff0c;我就已经写过静态版的通讯录了&#xff0c;只是存在着一些问题&#xff0c;具体细节可以详细看看我的静态版通讯录&#xff0c;好了&#xff0c;话不多说&…

计算机视觉知识点(一)——交并比(IoU)及其若干改进

交并比&#xff08;IoU&#xff09;前言IoU公式及示意图IoU Loss缺点GIoU Loss公式及示意图缺点DIoU公式及示意图CIoU前言 目标检测是一个常见的计算机视觉任务&#xff0c;在目标检测任务中&#xff0c;交并比作为评判检测框的标准具有很重要的意义&#xff0c;在实际的应用中…

【百面成神】java web基础7问,你能坚持到第几问

前 言 &#x1f349; 作者简介&#xff1a;半旧518&#xff0c;长跑型选手&#xff0c;立志坚持写10年博客&#xff0c;专注于java后端 ☕专栏简介&#xff1a;纯手打总结面试题&#xff0c;自用备用 &#x1f330; 文章简介&#xff1a;java web最基础、重要的8道面试题 文章目…

SAP 系统中过账码or记账码

SAP中过账码和记账码是指同一个事物。 在实际业务中&#xff0c;记账码就是只有“借”和“贷”&#xff0c; 而SAP中Posting Code肩负着更多的任务&#xff1a; 1&#xff09;界定科目类型&#xff0c; 2&#xff09;借贷方向&#xff0c; 3&#xff09;凭证输入时画面上的字…

运算放大器:电压比较器、电压跟随器、同相比例放大器

目录一、单限电压比较器二、滞回电压比较器三、窗口电压比较器四、正点原子直流电机驱动器电路分析实战1、电压采集电路2、电流采集电路3、过流检测电路Ⅰ、采用分压后的输入电压&#xff1a;Ⅱ、采用理想电压源的输入电压&#xff1a;Ⅲ、同相输入电压采用的是非理想电压源&am…

自考本科数据结构导论(02142)历年(应用题+算法题)真题汇总【20年4月-22年10月】

文章目录2020年4月应用题算法设计题2020年10月应用题算法设计题2021年4月应用题算法设计题2021年10月应用题算法设计题2022年4月应用题算法设计题2022年10月应用题算法设计题2020年4月 应用题 有二叉树如题29图所示,写出该二叉树的先序遍历、中序遍历和后序遍历序列。 如题…

AI真的快让我们失业了,从ChatGPT到Midjourney

参考文章&#xff1a; https://mp.weixin.qq.com/s/3RdHPPhYgDfB6KY6Y9Sk2A跟AI有关的新闻&#xff0c;一个接着一个。前一天你还和往常一样进入梦乡&#xff0c;第二天醒来就能被新的AI新闻“炸弹”震得心惊。 以ChatGPT为代表的AI语言模型&#xff0c;以Midjourney为代表的…

五、寄存器方式LED灯控制

寄存器方式LED灯控制 1、原理 电路图中相同网络标号表示它们是连接在一起&#xff0c;STM32F103ZET6的PC0-PC7 管脚连接D1-D8发光二极管阴极&#xff0c;如要使 D1 指示灯亮&#xff0c;只需控制 PC0 管脚输出低电平。 2、工程文件 Keil工程包含main.c、stm32f10x.h、start…

vue开发常用的工具有哪些

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。座右铭&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石&#xff0c;故能成其高。个人主页&#xff1a;小李会科技的…

开启新航路,拓尔思发力AIGC市场 | 爱分析调研

2022年&#xff0c;随着AI聊天机器人GhatGPT在世界范围内持续火爆&#xff0c;极具创意、表现力、个性化且能快速迭代的AIGC技术成功破圈&#xff0c;成为全民讨论热点。 AIGC是指在确定主题下&#xff0c;由算法模型自动生成内容&#xff0c;包括单模态内容如文本、图像、音频…

【Leetcode】队列的性质与应用

文章目录225. 用队列实现栈示例&#xff1a;提示&#xff1a;分析&#xff1a;题解&#xff1a;622. 设计循环队列示例&#xff1a;提示&#xff1a;分析&#xff1a;题解&#xff1a;225. 用队列实现栈 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&…

个人时间管理网站—Git项目管理

&#x1f31f;所属专栏&#xff1a;献给榕榕&#x1f414;作者简介&#xff1a;rchjr——五带信管菜只因一枚&#x1f62e;前言&#xff1a;该专栏系为女友准备的&#xff0c;里面会不定时发一些讨好她的技术作品&#xff0c;感兴趣的小伙伴可以关注一下~&#x1f449;文章简介…

面试官:如何保证接口幂等性?一口气说了9种方法!

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址 大家好&#xff0c;我是大彬~ 今…