NOIP模拟系列题目

题目列表:

跑步训练
金币
棋盘染色
矩形
o
Inside information

求大小为 n n n 的所有带标号无根树的直径的和模 P P P 的值。

1 ⩽ n ⩽ 500 , 1 0 8 ⩽ P ⩽ 2147483647 1 \leqslant n \leqslant 500,10^8 \leqslant P \leqslant 2147483647 1n500,108P2147483647
时间限制4s,空间限制1GB

首先枚举树的直径上节点数 d d d。如果 d d d 为偶数,则树可以看做两棵深度为 d 2 \frac{d}2 2d 的有根树合并得到的。设 f i , j f_{i,j} fi,j 为节点数为 i i i,深度不超过 j j j 的带标号有根树数量, g i , j g_{i,j} gi,j 为总节点数为 i i i,最大深度不超过 j j j 的带标号森林数量。则当 d d d 为偶数时答案为:
∑ i = 1 n − 1 ( f i , d 2 − f i , d 2 − 1 ) ( f n − i , d 2 − f n − i , d 2 − 1 ) ( n − 1 i ) \sum_{i=1}^{n-1}(f_{i,\frac{d}2}-f_{i,\frac{d}2-1})(f_{n-i,\frac{d}2}-f_{n-i,\frac{d}2-1})\binom{n-1}{i} i=1n1(fi,2dfi,2d1)(fni,2dfni,2d1)(in1)
d d d 为奇数时,则原树可以看做由一个点为根,并有至少两个儿子的子树深度为 d − 1 2 \frac{d-1}2 2d1 的树。统计答案时用深度为 d − 1 2 \frac{d-1}2 2d1 的森林方案数减去一棵深度为 d − 1 2 \frac{d-1}2 2d1,剩下的森林深度小于 d − 1 2 \frac{d-1}2 2d1 的树方案。则当 d d d 为奇数时的答案为:
n ( g n − 1 , d − 1 2 − g n − 1 , d − 1 2 − 1 − ∑ i = 1 n − 1 ( f i , d − f i , d − 1 ) g n − 1 − i , d − 1 ( n − 1 j ) ) n(g_{n-1,\frac{d-1}2}-g_{n-1,\frac{d-1}2-1}-\sum_{i=1}^{n-1}(f_{i,d}-f_{i,d-1})g_{n-1-i,d-1} \binom{n-1}{j}) n(gn1,2d1gn1,2d11i=1n1(fi,dfi,d1)gn1i,d1(jn1))
最后的问题就是求 f , g f,g f,g。根据定义,有DP式子:
f n , i = n g n − 1 , i − 1 f_{n,i}=n g_{n-1,i-1} fn,i=ngn1,i1
对于森林来说,算树按顺序加入的方案数:
g n , i = ∑ j = 1 n f j , i g n − j , i ( n − 1 n − i ) g_{n,i}=\sum_{j=1}^n f_{j,i} g_{n-j,i} \binom{n-1}{n-i} gn,i=j=1nfj,ignj,i(nin1)
时间复杂度 O ( n 3 ) O(n^3) O(n3),空间复杂度 O ( n 2 ) O(n^2) O(n2)

#include<iostream>
using namespace std;
#define R register int
#define U unsigned int
#define L long long
inline U Add(U x,const U y,U P){
	x+=y;
	return x<P?x:x-P;
}
U C[501][501],f[501][501],g[501][501];
int main(){
	int n,d;
	cin>>n;
	if(n==1){
		cout<<0;
		return 0;
	}
	U P,ans=0,cur;
	cin>>P;
	for(R i=0;i<=n;i++){
		C[i][0]=1;
		for(R j=1;j<=i;j++){
			C[i][j]=Add(C[i-1][j-1],C[i-1][j],P);
		}
	}
	g[0][0]=1;
	for(R i=1;i<=n;i++){
		for(R j=1;j<=n;j++){
			f[i][j]=(L)j*g[i-1][j-1]%P;
		}
		g[i][0]=1;
		for(R j=0;j<=n;j++){
			for(R k=1;k<=n-j;k++){
				g[i][j+k]=((L)g[i][j]*f[i][k]%P*C[j+k-1][j]+g[i][j+k])%P;
			}
		}
	}
	for(R i=1;i<=n;i++){
		d=i>>1;
		if((i&1)==1){
			cur=g[d][n-1];
			if(d!=0){
				cur=Add(cur,P-g[d-1][n-1],P);
			}
			for(R j=d;j!=n;j++){
				cur=Add(cur,P-(L)(f[d][j]+(d==0?0:P-f[d-1][j]))*g[d-1][n-1-j]%P*C[n-1][j]%P,P);
			}
			ans=((i-1ll)*cur*n+ans)%P;
		}else{
			cur=0;
			for(R j=d;j<=n-d;j++){
				cur=((L)(f[d][j]+P-f[d-1][j])*(f[d][n-j]+P-f[d-1][n-j])%P*C[n-1][j]+cur)%P;
			}
			ans=((i-1ll)*cur+ans)%P;
		}
	}
	cout<<ans;
	return 0;
}
跑步训练

给定一个 n n n 个点的树的平面图,共 m m m 个人各自从某些点向某个方向出发,每天一直沿着右手方向跑 p p p 条边,把最后的一条边封掉,第二天接着原来的方向继续跑步。求每个人最后在第几天被封在哪个节点,求每条边在第几天被哪个人封堵。

1 ⩽ n ⩽ 250000 , 0 ⩽ m ⩽ 50000 , 1 ⩽ p ⩽ 1 0 9 1 \leqslant n \leqslant 250000,0 \leqslant m \leqslant 50000,1 \leqslant p \leqslant 10^9 1n250000,0m50000,1p109
时间限制3s,空间限制512MB

在一棵树中一天的问题可以建立边的括号序来解决。用线段树维护括号序,一个人最后走的一条边可以在线段树上二分出来。根据每个人一天最后的节点将当前的数分为多棵子树,像虚树一样求出子树之间的祖先后代关系,将人分给子树,递归子树,最后将子树的括号序在线段树中删掉。代码细节很多。
时间复杂度 O ( ( n + m ) log ⁡ n ) O((n+m) \log n) O((n+m)logn),空间复杂度 O ( n + m ) O(n+m) O(n+m)

#include<stdio.h>
#include<vector>
#include<algorithm>
#include<map>
using namespace std;
#define R register int
#define I inline
#define N 250001
#define M 500002
struct Runner{
	int RunnerId,CurEdge;
};
I auto Pair(int d,int c){
	Runner res;
	res.RunnerId=d;
	res.CurEdge=c;
	return res;
}
vector<int>G[N];
int par[N],ansR[N][2],inE[N],vis[M],outE[N],ansE[N][2],Start[M],in[N],out[N],f[N],pos[N][2],End[M],dep[N],ct,tot;
bool dir[M];
I bool CompareDFN(int x,int y){
	return in[x]<in[y];
}
I int GetF(int x){
	if(par[x]==x){
		return x;
	}
	par[x]=GetF(par[x]);
	return par[x];
}
I void DFS(int x){
	tot++;
	in[x]=tot;
	dep[x]=dep[f[x]]+1;
	inE[x]=ct+1;
	for(int T:G[x]){
		ct++;
		pos[T][0]=ct;
		Start[ct]=x;
		End[ct]=T;
		DFS(T);
		ct++;
		pos[T][1]=ct;
		Start[ct]=T;
		End[ct]=x;
		dir[ct]=true;
	}
	out[x]=tot;
	outE[x]=ct;
}
struct SegmentNode{
	int Sum;
	bool tag;
}t[2000000];
I void Init(int p,int lf,int rt){
	t[p].Sum=rt-lf+1;
	if(lf!=rt){
		Init(p<<1,lf,lf+rt>>1);
		Init(p<<1|1,lf+rt+2>>1,rt);
	}
}
I void PutDown(int p,bool f){
	if(t[p].tag==true){
		t[p].Sum=0;
		if(f==true){
			t[p<<1].tag=t[p<<1|1].tag=true;
		}
		t[p].tag=false;
	}
}
I void Clear(int p,int lf,int rt,const int l,const int r){
	if(l<=lf&&rt<=r){
		t[p].tag=true;
	}else{
		PutDown(p,true);
		int mid=lf+rt>>1;
		if(l<=mid){
			Clear(p<<1,lf,mid,l,r);
		}
		if(r>mid){
			Clear(p<<1|1,mid+1,rt,l,r);
		}
		PutDown(p<<1,lf!=mid);
		PutDown(p<<1|1,mid+1!=rt);
		t[p].Sum=t[p<<1].Sum+t[p<<1|1].Sum;
	}
}
I int GetSum(int p,int lf,int rt,const int l,const int r){
	PutDown(p,lf!=rt);
	if(l<=lf&&rt<=r){
		return t[p].Sum;
	}
	int res=0,mid=lf+rt>>1;
	if(l<=mid){
		res=GetSum(p<<1,lf,mid,l,r);
	}
	if(r>mid){
		res+=GetSum(p<<1|1,mid+1,rt,l,r);
	}
	return res;
}
I int Find(int p,int lf,int rt,const int l,const int r,int&k){
	PutDown(p,lf!=rt);
	if(l<=lf&&rt<=r&&t[p].Sum<k){
		k-=t[p].Sum;
		return-1;
	}
	if(lf==rt){
		return lf;
	}
	int mid=lf+rt>>1,res=-1;
	if(l<=mid){
		res=Find(p<<1,lf,mid,l,r,k);
	}
	if(res!=-1){
		return res;
	}
	return Find(p<<1|1,mid+1,rt,l,r,k);
}
I void Solve(int root,vector<Runner>&C,int Time,const int p){
	int El=inE[root],Er=outE[root],sum;
	if(C.empty()==true){
		return Clear(1,1,ct,El-1,Er+1);
	}
	sum=GetSum(1,1,ct,El,Er);
	if(sum==0){
		for(auto T:C){
			ansR[T.RunnerId][0]=Time;
			ansR[T.RunnerId][1]=root;
		}
		return Clear(1,1,ct,El-1,Er+1);
	}
	vector<int>A;
	A.push_back(root);
	Time++;
	for(auto&T:C){
		int c=T.CurEdge,rk;
		if(c<El||c>Er){
			rk=0;
		}else{
			rk=GetSum(1,1,ct,El,c);
		}
		rk=(rk+p)%sum;
		if(rk==0){
			rk=sum;
		}
		int cur=Find(1,1,ct,El,Er,rk),b;
		b=dir[cur]==true?Start[cur]:End[cur];
		ansE[b][0]=Time;
		if(vis[cur]!=0){
			par[GetF(T.RunnerId)]=vis[cur];
			T.CurEdge=0;
		}else{
			if(ansE[b][1]==0){
				ansE[b][1]=T.RunnerId;
			}
			A.push_back(b);
			T.CurEdge=cur;
			vis[cur]=T.RunnerId;
		}
	}
	sort(A.begin(),A.end(),CompareDFN);
	int s=unique(A.begin(),A.end())-A.begin();
	map<int,int>Q;
	for(R i=0;i!=s;i++){
		Q[A[i]]=i;
	}
	vector<int>B(s),F(s);
	int Top=0;
	B[0]=0;
	for(R i=1;i!=s;i++){
		while(Top!=0){
			int x=B[Top];
			if(in[A[x]]<in[A[i]]&&in[A[i]]<=out[A[x]]){
				break;
			}
			F[B[Top]]=B[Top-1];
			Top--;
		}
		Top++;
		B[Top]=i;
	}
	while(Top!=0){
		F[B[Top]]=B[Top-1];
		Top--;
	}
	vector<vector<Runner>>D(s);
	for(auto T:C){
		int x=T.CurEdge;
		if(x!=0){
			if(dir[x]==true){
				D[F[Q[Start[x]]]].push_back(T);
			}else{
				D[Q[End[x]]].push_back(T);
			}
		}
	}
	for(R i=s-1;i!=-1;i--){
		Solve(A[i],D[i],Time,p);
	}
	if(root!=1){
		Clear(1,1,ct,El-1,Er+1);
	}
}
int main(){
	int n,m,p,a,b;
	scanf("%d%d%d",&n,&m,&p);
	for(int i=2;i<=n;i++){
		scanf("%d",f+i);
		G[f[i]].push_back(i);
	}
	vector<Runner>C;
	DFS(1);
	for(R i=1;i<=m;i++){
		scanf("%d%d",&a,&b);
		int x;
		if(a==f[b]){
			x=pos[b][0]-1;
		}else{
			x=pos[a][1]-1;
		}
		if(x==0){
			x=ct;
		}
		C.push_back(Pair(i,x));
		par[i]=i;
	}
	Init(1,1,ct);
	Solve(1,C,0,p);
	for(R i=1;i<=m;i++){
		int x=GetF(i);
		printf("%d %d\n",ansR[x][0],ansR[x][1]);
	}
	for(R i=2;i<=n;i++){
		printf("%d %d\n",ansE[i][0],ansE[i][1]);
	}
	return 0;
}
金币

现有若干袋金币,每袋有 k k k 个金币,其中一个袋子里全是假金币,其他袋子里都是真金币,假金币比真的轻。每次用天平称金币,要求天平两边的金币总数相同,称完可以得到两边的金币重量差。给定称量次数限制 n n n,求最多可以在多少袋子中确定装假金币的袋子。输出答案模 1000000007 1000000007 1000000007的值。

1 ⩽ n ⩽ 1 0 9 , 1 ⩽ k ⩽ 1 0 7 1 \leqslant n \leqslant 10^9,1 \leqslant k \leqslant 10^7 1n109,1k107
时间限制1s,空间限制512MB

每个袋子在每次称量中共有 2 k + 1 2k+1 2k+1 中放在天平上的方法。若所有称量选取的金币数量最大公约数大于 1 1 1,那么就不能分辨出袋子之间的组合,因为看到的现象是本质相同的,所以答案就是本质不同的现象个数,即为:
∑ − k ⩽ a i ⩽ k [ ∣ gcd ⁡ ( a ) ∣ < 2 ] \sum_{-k \leqslant a_i \leqslant k} [|\gcd(a)|<2] kaik[gcd(a)<2]
莫比乌斯反演并枚举非 0 0 0数的个数:
1 + ∑ i = 1 n ( n i ) ∑ d = 1 k μ ( d ) [ k d ] i 2 i 1+\sum_{i=1}^n \binom{n}{i} \sum_{d=1}^k \mu(d) [\frac{k}{d}]^i 2^i 1+i=1n(in)d=1kμ(d)[dk]i2i
改变求和顺序:
1 + ∑ d = 1 k μ ( d ) ∑ i = 1 n ( n i ) ( 2 [ k d ] ) i 1+\sum_{d=1}^k \mu(d) \sum_{i=1}^n \binom{n}{i} (2[\frac{k}{d}])^i 1+d=1kμ(d)i=1n(in)(2[dk])i
根据二项式定理得到:
1 + ∑ d = 1 k μ ( d ) ( ( 2 [ k d ] + 1 ) n − 1 ) 1+\sum_{d=1}^k \mu(d)((2[\frac{k}{d}]+1)^n-1) 1+d=1kμ(d)((2[dk]+1)n1)
用整出分块即可。
时间复杂度 O ( k log ⁡ 2 n ) O(\sqrt k \log_2 n) O(k log2n),空间复杂度 O ( k ) O(k) O(k)

#include<stdio.h>
#define R register int
#define L long long
#define P 1000000007
inline int PowMod(int x,int y){
	int res=1;
	while(y!=0){
		if((y&1)==1){
			res=(L)res*x%P;
		}
		y>>=1;
		x=(L)x*x%P;
	}
	return res;
}
int mu[10000001],prime[664579];
bool vis[10000001];
int main(){
	mu[1]=1;
	int n,k,t=0,r,ans=1;
	scanf("%d%d",&n,&k);
	for(R i=2;i<=k;i++){
		if(vis[i]==false){
			prime[t]=i;
			mu[i]=-1;
			t++;
		}
		for(R j=0;prime[j]*i<=k;j++){
			vis[prime[j]*i]=true;
			if(i%prime[j]==0){
				mu[i*prime[j]]=0;
				break;
			}
			mu[i*prime[j]]=-mu[i];
		}
		mu[i]+=mu[i-1];
	}
	for(R i=1;i<=k;i=r+1){
		t=k/i;
		r=k/t;
		ans=((PowMod(t<<1|1,n)-1ll+P)*(mu[r]-mu[i-1]+P)+ans)%P;
	}
	printf("%d",ans);
	return 0;
}
棋盘染色

一个 n × m n \times m n×m 的棋盘,开始有些格子被染上了红色或者蓝色,有些格子没有颜色。将所有未染色的格子染上红色或者蓝色,使得棋盘中每个长宽都为偶数的矩形里面红色蓝色出现个数相同。求染色的方案数,答案对 1000000007 1000000007 1000000007取模。

1 ⩽ n , m ⩽ 1000 1 \leqslant n,m \leqslant 1000 1n,m1000
时间限制1s,空间限制128MB

观察规律可以发现矩阵填完颜色后要么所有行红蓝交替,要么所有列红蓝交替。所以答案就是行交替加列交替的方案数减行列都交替的方案数。
时空复杂度 O ( n m ) O(nm) O(nm)

#include<stdio.h>
#define R register int
#define P 1000000007
inline int Trans(const char c){
	return c=='R';
}
char s[1000][1001];
int t[1500];
int main(){
	int n,m,x,cur=1,ans=0;
	scanf("%d%d",&n,&m);
	for(R i=0;i!=n;i++){
		scanf("%s",s[i]);
		t[i]=2;
	}
	for(R i=0;i!=n;i++){
		for(R j=0;j!=n;j++){
			if(s[i][j]!='?'){
				x=Trans(s[i][j])^j&1;
				if(t[i]==2){
					t[i]=x;
				}else if(t[i]!=x){
					goto BreakR;
				}
			}
		}
	}
	for(R i=0;i!=n;i++){
		if(t[i]==2){
			cur<<=1;
			if(cur>P){
				cur-=P;
			}
		}
	}
	ans=cur;
	BreakR:;
	for(R i=0;i!=n;i++){
		t[i]=2;
	}
	for(R i=0;i!=n;i++){
		for(R j=0;j!=n;j++){
			if(s[i][j]!='?'){
				x=Trans(s[i][j])^i&1;
				if(t[j]==2){
					t[j]=x;
				}else if(t[j]!=x){
					goto BreakC;
				}
			}
		}
	}
	cur=1;
	for(R i=0;i!=n;i++){
		if(t[i]==2){
			cur<<=1;
			if(cur>P){
				cur-=P;
			}
		}
	}
	ans+=cur;
	if(ans>=P){
		ans-=P;
	}
	BreakC:;
	cur=2;
	for(R i=0;i!=n;i++){
		for(R j=0;j!=n;j++){
			if(s[i][j]!='?'){
				x=Trans(s[i][j])^(i^j)&1;
				if(cur==2){
					cur=x;
				}else if(cur!=x){
					goto BreakT;
				}
			}
		}
	}
	ans-=cur==0?1:cur;
	if(ans<0){
		ans+=P;
	}
	BreakT:;
	printf("%d",ans);
	return 0;
}
矩形

给出一个 n × n n \times n n×n 的网格,有些格子是黑色的剩下是白色的。有 m m m 次询问,每次询问网格中有多少 a a a b b b 列的矩形,满足它的四条边上所有格子都是黑的。

1 ⩽ n , m ⩽ 1500 1 \leqslant n,m \leqslant 1500 1n,m1500
时间限制5s,空间限制128MB

对询问离线,枚举矩形的上边界,再从小到大枚举矩形下边界,用bitset处理列方向上的限制。对于行方向的限制预处理bitset倍增数组,每次询问可以倍增地表示出一段 1 1 1
时间复杂度 O ( m n 2 log ⁡ 2 n ) O(mn^2 \log_2n) O(mn2log2n),空间复杂度 O ( n 2 log ⁡ 2 n + m ) O(n^2 \log_2n+m) O(n2log2n+m)

#include<stdio.h>
#include<bitset>
#include<vector>
using namespace std;
#define R register int
#define I inline
char s[1500][1501];
int ans[1500];
struct Query{
	int Id,Qb;
};
I auto Pair(int d,int b){
	Query res;
	res.Id=d;
	res.Qb=b;
	return res;
}
vector<Query>Q[1500];
bitset<1500>F[1500][11];
int main(){
	int n,m,a,b;
	scanf("%d%d",&n,&m);
	for(R i=0;i!=n;i++){
		scanf("%s",s[i]);
		for(R j=0;j!=n;j++){
			F[i][0][j]=s[i][j]=='1';
		}
		for(R j=1;1<<j<=n;j++){
			F[i][j]=F[i][j-1]&F[i][j-1]>>(1<<j-1);
		}
	}
	for(R i=0;i!=m;i++){
		scanf("%d%d",&a,&b);
		Q[a-1].push_back(Pair(i,b));
	}
	for(R i=0;i!=n;i++){
		bitset<1500>C;
		for(R j=0;j!=n;j++){
			C[j]=1;
		}
		for(R j=i;j!=n;j++){
			C&=F[j][0];
			for(auto T:Q[j-i]){
				b=T.Qb-1;
				a=1;
				bitset<1500>B=F[i][0]&F[j][0];
				for(R k=0;k!=11;k++){
					if((b>>k&1)==1){
						B&=(F[i][k]&F[j][k])>>a;
						a+=1<<k;
					}
				}
				ans[T.Id]+=(C&C>>b&B).count();
			}
		}
	}
	for(R i=0;i!=m;i++){
		printf("%d\n",ans[i]);
	}
	return 0;
}

将倍增改为ST表,可以将时间复杂度优化到 O ( n 2 m ) O(n^2 m) O(n2m)

o

有一个无限大的网格图,在第 0 0 0秒的时候,有 n n n 个格子会着火,每隔一秒,一个着火的格子会导致和它八连通的未着火的格子着火,定义一个着火格子的权值为它最早着火的时间,求 t t t 秒之后所有着火格子的权值和。答案对 998244353 998244353 998244353取模。

1 ⩽ n ⩽ 50 , 1 ⩽ t ⩽ 1 0 8 1 \leqslant n \leqslant 50,1 \leqslant t \leqslant 10^8 1n50,1t108
时间限制2s,空间限制512MB

首先答案可以转化为求时间 i i i 覆盖的面积的前缀和。对于一段没有出现新的重叠的部分,面积为对时间的二次函数,前缀和为对时间的三次函数。求对于一个时间的覆盖面积为若干矩形的面积并,可以用扫描线求。最后对每段进行拉格朗日插值即可。
时间复杂度 O ( n 4 ) O(n^4) O(n4),空间复杂度 O ( n 2 ) O(n^2) O(n2)

#include<stdio.h>
#include<set>
#include<vector>
#include<algorithm>
using namespace std;
#define R register int
#define L long long
#define I inline
#define P 998244353
I int Add(int x,const int y){
	x+=y;
	return x<P?x:x-P;
}
I int GetInv(int x){
	int res=1,y=P-2;
	while(y!=0){
		if((y&1)==1){
			res=(L)res*x%P;
		}
		x=(L)x*x%P;
		y>>=1;
	}
	return res;
}
int px[50],py[50],distX[100],distY[100],sum[100];
I int Dis(int x,int y){
	int a=px[x]-px[y],b=py[x]-py[y];
	a=a<0?-a:a;
	b=b<0?-b:b;
	return a>b?a:b;
}
struct Item{
	int Lf,Rt;
};
I Item Pair(int l,int r){
	Item res;
	res.Lf=l;
	res.Rt=r;
	return res;
}
vector<Item>Pos[100],Neg[100];
I void Modify(int l,int r,const int d){
	for(R i=l;i!=r;i++){
		sum[i]+=d;
	}
}
I int GetSum(const int n){
	int res=0;
	for(R i=0;i!=n-1;i++){
		if(sum[i]!=0){
			res=Add(res,distX[i+1]-distX[i]);
		}
	}
	return res;
}
I int Calc(int t,const int n){
	int res=0,tx,ty,l,r,x;
	for(R i=0;i!=n;i++){
		distX[i<<1]=px[i]-t;
		distX[i<<1|1]=px[i]+t+1;
		distY[i<<1]=py[i]-t;
		distY[i<<1|1]=py[i]+t+1;
	}
	sort(distX,distX+(n<<1));
	tx=unique(distX,distX+(n<<1))-distX;
	sort(distY,distY+(n<<1));
	ty=unique(distY,distY+(n<<1))-distY;
	for(R i=0;i!=tx;i++){
		sum[i]=0;
	}
	for(R i=0;i!=ty;i++){
		Pos[i].clear();
		Neg[i].clear();
	}
	for(R i=0;i!=n;i++){
		l=lower_bound(distX,distX+tx,px[i]-t)-distX;
		r=lower_bound(distX,distX+tx,px[i]+t+1)-distX;
		x=lower_bound(distY,distY+ty,py[i]-t)-distY;
		Pos[x].push_back(Pair(l,r));
		x=lower_bound(distY,distY+ty,py[i]+t+1)-distY;
		Neg[x].push_back(Pair(l,r));
	}
	for(R i=0;i!=ty-1;i++){
		for(Item T:Neg[i]){
			Modify(T.Lf,T.Rt,-1);
		}
		for(Item T:Pos[i]){
			Modify(T.Lf,T.Rt,1);
		}
		res=((L)(distY[i+1]-distY[i])*GetSum(tx)+res)%P;
	}
	return res;
}
I int Solve(int l,int r,const int n){
	int res=0;
	if(r-l<3){
		for(R i=l;i<=r;i++){
			res=Add(res,Calc(i,n));
		}
	}else{
		int a[4],fl=0,fr=0;
		for(R i=0;i!=4;i++){
			a[i]=Calc(l+i,n);
		}
		for(R i=1;i!=4;i++){
			a[i]=Add(a[i-1],a[i]);
		}
		for(R i=0;i!=4;i++){
			int pl=1,pr=1,inv=1;
			for(R j=0;j!=4;j++){
				if(i!=j){
					pl=(-1ll-j)*pl%P;
					inv=(L)(i-j)*inv%P;
					pr=(L)(r-l-j)*pr%P;
				}
			}
			inv=GetInv(inv);
			fl=((L)inv*pl%P*a[i]+fl)%P;
			fr=((L)inv*pr%P*a[i]+fr)%P;
		}
		if(fl<0){
			fl+=P;
		}
		if(fr<0){
			fr+=P;
		}
		res=Add(fr,P-fl);
	}
	return res;
}
int main(){
	int n,t,d,ans=0;
	scanf("%d%d",&n,&t);
	if(t==0){
		printf("0");
		return 0;
	}
	for(R i=0;i!=n;i++){
		scanf("%d%d",px+i,py+i);
	}
	set<int>S;
	S.insert(0);
	S.insert(t+1);
	for(R i=0;i!=n;i++){
		for(R j=0;j!=n;j++){
			d=Dis(i,j);
			if(d>1&&d+1<=t<<1){
				S.insert(d+1>>1);
			}
		}
	}
	vector<int>A;
	for(auto T:S){
		A.push_back(T);
	}
	for(R i=1;i!=A.size();i++){
		ans=Add(ans,Solve(A[i-1],A[i]-1,n));
	}
	printf("%d",((1ll+t)*Calc(t,n)-ans+P)%P);
	return 0;
}

Inside information
对于服务器中的数据可以用线段树合并来维护。对数据所在的服务器考虑倒着维护。线段树的下标表示这个时间可以到新的服务器。这也是个线段树合并。查询的答案即为线段树上的前缀和。
时空复杂度 O ( ( n + k ) log ⁡ 2 n ) O((n+k) \log_2 n) O((n+k)log2n)

#include<stdio.h>
#include<vector>
using namespace std;
#define R register int
#define I inline
#define N 240001
int root[N],ans[N],ct;
struct SegmentNode{
	int Ls,Rs,Sum;
}t[30000000];
I void GetNode(int&x){
	ct++;
	x=ct;
	t[x].Ls=t[x].Rs=0;
}
I void Insert(int p1,int p2,int lf,int rt,const int x){
	t[p2].Sum=t[p1].Sum+1;
	if(lf!=rt){
		int mid=lf+rt>>1;
		if(x>mid){
			GetNode(t[p2].Rs);
			t[p2].Ls=t[p1].Ls;
			Insert(t[p1].Rs,t[p2].Rs,mid+1,rt,x);
		}else{
			GetNode(t[p2].Ls);
			t[p2].Rs=t[p1].Rs;
			Insert(t[p1].Ls,t[p2].Ls,lf,mid,x);
		}
	}
}
I int Merge(int x,int y){
	if(x==0||y==0){
		return x|y;
	}
	int p;
	GetNode(p);
	t[p].Ls=Merge(t[x].Ls,t[y].Ls);
	t[p].Rs=Merge(t[x].Rs,t[y].Rs);
	t[p].Sum=t[t[p].Ls].Sum+t[t[p].Rs].Sum;
	return p;
}
I bool Query(int p,int lf,int rt,const int x){
	if(lf==rt){
		return t[p].Sum==1;
	}
	int mid=lf+rt>>1;
	if(x>mid){
		if(t[p].Rs==0){
			return false;
		}
		return Query(t[p].Rs,mid+1,rt,x);
	}
	if(t[p].Ls==0){
		return false;
	}
	return Query(t[p].Ls,lf,mid,x);
}
I int GetSum(int p,int lf,int rt,const int r){
	if(rt<=r){
		return t[p].Sum;
	}
	int mid=lf+rt>>1,res=0;
	if(r>mid&&t[p].Rs!=0){
		res=GetSum(t[p].Rs,mid+1,rt,r);
	}
	if(t[p].Ls!=0){
		res+=GetSum(t[p].Ls,lf,mid,r);
	}
	return res;
}
struct Item{
	int QA,QB,Id;
};
I auto Pair(int a,int b,int d){
	Item res;
	res.QA=a;
	res.QB=b;
	res.Id=d;
	return res;
}
vector<Item>Q;
int main(){
	int n,m,a,b,x;
	scanf("%d%d",&n,&m);
	m+=n-1; 
	for(int i=1;i<=n;i++){
		GetNode(root[i]);
		Insert(0,root[i],1,n,i);
	}
	for(R i=1;i<=m;i++){
		char opt;
		scanf("\n%c",&opt);
		scanf("%d",&a);
		if(opt=='S'){
			scanf("%d",&b);
			root[a]=root[b]=Merge(root[a],root[b]);
			Q.push_back(Pair(a,b,i));
		}else if(opt=='Q'){
			scanf("%d",&b);
			if(Query(root[a],1,n,b)==true){
				ans[i]=-1;
			}else{
				ans[i]=-2;
			}
		}else{
			Q.push_back(Pair(a,0,i));
			ans[i]=1;
		}
	}
	ct=0;
	for(R i=1;i<=n;i++){
		GetNode(root[i]);
	}
	for(auto T=Q.rbegin();T!=Q.rend();T++){
		if(T->QB!=0){
			a=T->QA;
			b=T->QB;
			root[a]=root[b]=Merge(root[T->QA],root[T->QB]);
			GetNode(x);
			Insert(root[a],x,1,m,T->Id);
			root[a]=root[b]=x;
		}
	}
	for(auto T:Q){
		if(T.QB==0){
			ans[T.Id]+=GetSum(root[T.QA],1,m,T.Id);
		}
	}
	for(R i=1;i<=m;i++){
		if(ans[i]==-1){
			puts("yes");
		}else if(ans[i]==-2){
			puts("no");
		}else if(ans[i]!=0){
			printf("%d\n",ans[i]);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值