NOIP2020模拟赛(二十五)7.26 结题报告 A.求和 B.排队 C.城市

link

A.求和
大致意思就是求

[sum_{i=1}^{n} sum_{j=1}^{m} lbrack i eq j brack(n ; mod; i)(m ; mod ; j) ]

solve

这题我们要从模的本质出发:

[a quad mod quad b = a - b imes lfloor frac{a}{b} floor ]

我们首先不考虑(i eq j)条件,单独对(i)(j)讨论,我们可以得到

[sum_{i = 1}^{n}n quad mod quad i=sum_{i = 1}^{n}n-i imes lfloor frac{n}{i} floor ]

化简一下,可以得到

[sum_{i = 1}^{n}n-i imes lfloor frac{n}{i} floor = n^{2}-sum_{i=1}^{n}i lfloor frac{n}{i} floor ]

由与(lfloor frac{n}{i} floor)不同的只有(sqrt{n})个,这个式子可以(O(sqrt{n}))计算。对于(i),(j)分别做一遍相乘起来即可。

对于(i=j)的部分,单独拿出来减去贡献即可。我们用类似的方法,即求

[sum_{i=1}^{min(n,m)}(nquad modquad i)(mquad modquad i) ]

然后跟之前一样的处理方法化为求

[sum_{i=1}^{min(n,m)}(n-i imes lfloor frac{n}{i} floor)(m - i imes lfloor frac{m}{i} floor) ]

稍微化简一下就可以也(sqrt{n})分块了

[sum_{i=1}^{min(n,m)}(nm - mi lfloor frac{n}{i} floor - ni lfloor frac{m}{i} floor + i^{2}lfloor frac{m}{i} floor lfloor frac{n}{i} floor) ]

最后相减就好了

code

#include<iostream>
#include<cstdio>
#define ll long long
#define MO 19940417
#define si 3323403
using namespace std;
ll n,m,Sn,Sm,S3,Ans;
ll Mod(ll x){
	if (x<0) return x+MO;
	if (x>=MO) return x-MO;
	return x;
}
int main(){
	scanf("%lld%lld",&n,&m);
	if (n>m) swap(n,m);
	Sn=n*n%MO;Sm=m*m%MO;S3=Sn*m%MO;
	for (ll i=1,j;i<=n;i=j+1){
		j=min(n,n/(n/i));
		Sn=Mod(Sn-(n/i)*((i+j)*(j-i+1)/2%MO)%MO);
	}
	for (ll i=1,j;i<=m;i=j+1){
		j=min(m,m/(m/i));
		Sm=Mod(Sm-(m/i)*((i+j)*(j-i+1)/2%MO)%MO);
	}
	
	for (ll i=1,j;i<=n;i=j+1){
		j=min(n,min(n/(n/i),m/(m/i)));
		S3=Mod(S3-(m/i*n+n/i*m)%MO*((i+j)*(j-i+1)/2%MO)%MO+(n/i)*(m/i)%MO*Mod(j*(j+1)%MO*(2*j+1)%MO-(i-1)*i%MO*(2*i-1)%MO)%MO*si%MO);
	}
	printf("%lld
",Mod(Sn*Sm%MO-S3));
	return 0;
}

B.排队

link

B.排队

solve

看到这道题,第一反应就是这个关系是一棵树

对于每一个节点的不同儿子,先后顺序有着很大的影响,为了消除这种影响,我们先进行一道转化,先可以把比 (x_i) 级别小的所有节点都插入到 (x_i) 的子树中,这样就方便统计了。

如果一个结点有两个儿子l,r,并且l排在r的前面,那么总的方法数就是(f[l]*f[r]*C(size[l]+size[r],size[l]-1))

其含义是:容易想到,根据乘法原理,总共会有(f[l]*f[r])种情况,对于每种情况,都可以有(C(size[l]+size[r],size[l]-1))种合并方法

子树总共有(size[l]+size[r])个结点(不算根结点),所以空的位置就是(size[l]+size[r])个,其中属于(l)的总共就有(size[l]-1)个,这个就直接用组合数就算出来了

然后再一个个往后合并就好了

code

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int TT=10007;
const int maxn=1005,maxe=2005;
int N,lnk[maxn],nxt[maxe],son[maxe],cnt,size[maxn],F[maxn],T,C[maxn<<1][maxn<<1],lst;
inline int read(){
	int ret=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
	while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
inline void add_e(int x,int y){son[++cnt]=y;nxt[cnt]=lnk[x];lnk[x]=cnt;}
void DFS(int x){
	size[x]=1;F[x]=1;
	for(int j=lnk[x];j;j=nxt[j]){
		DFS(son[j]);
		size[x]+=size[son[j]];
		F[x]=F[x]*(F[son[j]])%TT;
		if(j!=lnk[x])F[x]=(F[x]*C[size[x]-1][size[son[j]]])%TT;
	}
	return ;
}
int main(){
//	freopen("c.in","r",stdin);
//	freopen("c.out","w",stdout);
	T=read();
	C[0][0]=1;
	for(int i=1;i<=2000;i++)
	for(int j=0;j<=2000;j++)C[i][j]=i==j||j==0?1:(C[i-1][j]+C[i-1][j-1])%TT;
	while(T--){
		cnt=0;
		memset(F,0,sizeof F);
		memset(lnk,0,sizeof lnk);
		memset(nxt,0,sizeof nxt);
		memset(son,0,sizeof son);
		memset(size,0,sizeof size);
		N=read();
		for(int i=1;i<=N;i++){
			int p=read();lst=i;
			for(int j=1;j<=p;j++){
				int x=read();
				add_e(lst,x);lst=x;
			}
		}
		DFS(1);
		printf("%d
",F[1]);
	}
	return 0;
}

C.城市

link

C.城市

solve

仔细读这道题,我们发现就是找一个环上点的最大值最小值。

显然,贪心的想法,我们先建一颗最小生成树,然后我们再加一条边就变成一个环了,我们加边也由小到大来加

每加一条边,我们就要将边所对应的的两个点在树上的路径赋成这条边的权值,但是这样显然是超时的,要考虑怎么优化。

我们可以用并查集来链接,这样就能保证每条边的遍历次数大大减小

对于询问,每两个点之间的最大权值,我们用LCA来维护,当我们查询时发现x和y不连通,即是无解。

#include<cstdio>
#include<cctype>
#include<algorithm>
const int maxn=100005,maxe=500005;
int n,m,q,w[maxn],lgn;struct Y{int x,y,z;}e[maxe];
int fa[maxn],pa[maxn][20],dep[maxn],f[maxn][20];
int son[maxn<<1],lnk[maxn],lst[maxn<<1],tot;bool vis[maxe];
inline int red(){
	int ret=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) ret=(ret<<3)+(ret<<1)+(ch^48),ch=getchar();
	return ret;
}
inline int abs_(int x){return x>0? x:-x;}
inline bool cmp(Y x,Y y){return x.z<y.z;}
inline int max_(int x,int y){return x>y? x:y;}
int gt(int x){return x==fa[x]? x:fa[x]=gt(fa[x]);}
inline int lg(int x){int r=-1;while(x) r++,x>>=1;return r;}
inline void e_(int x,int y){son[++tot]=y;lst[tot]=lnk[x];lnk[x]=tot;}
void DFS(int x){
	for(int j=lnk[x];j;j=lst[j]) if(son[j]!=pa[x][0])
	 dep[son[j]]=dep[x]+1,pa[son[j]][0]=x,DFS(son[j]);
}
inline int LCA(int x,int y){
	int ret=0;
	if(dep[x]<dep[y]) x^=y^=x^=y;
	for(int i=lgn;~i;i--){
		if(dep[pa[x][i]]>=dep[y])
		 ret=max_(ret,f[x][i]),x=pa[x][i];
		if(x==y) return ret;
	}
	for(int i=lgn;~i;i--) if(pa[x][i]!=pa[y][i])
	 ret=max_(ret,max_(f[x][i],f[y][i])),x=pa[x][i],y=pa[y][i];
	return max_(ret,max_(f[x][0],f[y][0]));
}
int main(){
	n=red(),m=red(),q=red();lgn=lg(n);
	for(int i=1;i<=n;i++) w[fa[i]=i]=red();
	for(int i=1,x,y;i<=m;i++){
		x=red(),y=red();
		e[i]=(Y){x,y,abs_(w[x]-w[y])};
	}
	std::sort(e+1,e+1+m,cmp);
	for(int i=1,fx,fy;i<=m;i++){
		fx=gt(e[i].x);fy=gt(e[i].y);
		if(fx==fy){vis[i]=1;continue;}
		fa[fx]=fy;e_(e[i].x,e[i].y);e_(e[i].y,e[i].x);
	}
	DFS(1);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1,x,y;i<=m;i++) if(vis[i]){
		x=gt(e[i].x),y=gt(e[i].y);
		while(x!=y){
			if(dep[x]<dep[y]) x^=y^=x^=y;
			f[x][0]=e[i].z;x=fa[x]=gt(pa[x][0]);
		}
	}
	for(int j=1;j<=lgn;j++) for(int i=1;i<=n;i++)
	 pa[i][j]=pa[pa[i][j-1]][j-1],f[i][j]=max_(f[i][j-1],f[pa[i][j-1]][j-1]);
	for(int x,y;q--;){
		x=red();y=red();
		if(gt(x)!=gt(y)) printf("infinitely
");
		else printf("%d
",LCA(x,y));
	}
	return 0;
}