NOI模拟赛记录

题目:
差不多得了
模拟
计算几何
偶数图
矩阵的秩
第五题
猜数字
字符串

差不多得了

给定平面内 n n n 个点坐标为 ( x i , y i ) (x_i,y_i) (xi,yi),求每个点到其他点的距离之和。要求精度达到 1 0 − 4 10^{-4} 104
1 ⩽ n ⩽ 200000 1 \leqslant n \leqslant 200000 1n200000
0 ⩽ x i , y i ⩽ 1 0 9 0 \leqslant x_i,y_i \leqslant 10^9 0xi,yi109
时间限制1s,空间限制512MB

由于一条线段在各个方向的直线上的投影长度的积分与原长成正比,所以将 π \pi π分为 k k k 份分别计算,模拟积分即可。
k k k 取100左右比较合适。
时间复杂度 O ( n k ) O(nk) O(nk),空间复杂度 O ( n ) O(n) O(n)

#include<stdio.h>
#include<math.h>
#include<algorithm>
#define R register int
#define D double
#define N 200000
#define PIE 3.141592653589793
D len[N],a[N],ans[N],pre[N];
struct Point{
	int Id;
	D Pos;
	inline friend bool operator<(Point A,Point B){
		return A.Pos<B.Pos;
	}
}p[N];
int main(){
	int n,x,y;
	scanf("%d",&n);
	for(R i=0;i!=n;i++){
		scanf("%d%d",&x,&y);
		a[i]=atan2(y,x);
		len[i]=sqrt((long long)x*x+(long long)y*y);
	}
	D sum=0,the,tot;
	for(R i=0;i!=100;i++){
		the=PIE*i*.01;
		sum+=fabs(cos(the));
		for(R j=0;j!=n;j++){
			p[j].Id=j;
			p[j].Pos=len[j]*cos(a[j]-the);
		}
		std::sort(p,p+n);
		pre[0]=p[0].Pos;
		for(R j=1;j!=n;j++){
			pre[j]=pre[j-1]+p[j].Pos;
		}
		tot=pre[n-1];
		for(R j=0;j!=n;j++){
			ans[p[j].Id]+=p[j].Pos*(j+1)-pre[j]+(tot-pre[j])-p[j].Pos*(n-j-1);
		}
	}
	for(R i=0;i!=n;i++){
		printf("%.9lf\n",ans[i]/sum);
	}
	return 0;
}
模拟

一骰子底面在平面为以 ( 0 , 0 ) , ( 1 , 1 ) (0,0),(1,1) (0,0),(1,1) 为对角线的正方形,沿着直线 y = b a x y=\frac{b}{a}x y=abx 的方向向右或向上翻滚骰子,求骰子从起始位置到以 ( a − 1 , b − 1 ) , ( a , b ) (a-1,b-1),(a,b) (a1,b1),(a,b) 为对角线的正方形的位置路径上底面的数字之和。
骰子六个面的数字为 1 ∼ 6 1 \sim 6 16 的排列。
1 ⩽ a , b ⩽ 1 0 18 1 \leqslant a,b \leqslant 10^{18} 1a,b1018
时间限制1s,空间限制512MB

不妨设 a ⩾ b a \geqslant b ab,则每向上翻滚一次要至少向右翻滚 [ a b ] [\frac{a}{b}] [ba] 次,因此可以将向右翻滚 [ a b ] [\frac{a}{b}] [ba] 次然后向上翻滚一次替换为原来的向上操作,因此一个 ( a , b ) (a,b) (a,b) 的问题就变为了 ( b , a m o d    b ) (b,a \mod b) (b,amodb) 的问题。
a ⩽ b a \leqslant b ab 的情况同理。
中途求置换的幂可以倍增求得。
时空复杂度 O ( log ⁡ 2 ( a + b ) ) O(\log_2(a+b)) O(log2(a+b))

#include<stdio.h>
#define R register int
#define L unsigned long long
#define I inline
I void Swap(int&x,int&y){
	int t=x;
	x=y;
	y=t;
}
struct Roll{
	int per[6];
	L sum[6];
	I void Init(const int a[6],const L b[6]){
		for(R i=0;i!=6;i++){
			per[i]=a[i];
			sum[i]=b[i];
		}
	}
};
I Roll RollMul(Roll A,Roll B){
	Roll C;
	for(R i=0;i!=6;i++){
		C.sum[i]=A.sum[i]+B.sum[A.per[i]];
		C.per[i]=B.per[A.per[i]];
	}
	return C;
}
I Roll RollPow(Roll A,L n){
	Roll S;
	for(R i=0;i!=6;i++){
		S.per[i]=i;
		S.sum[i]=0;
	}
	while(n!=0){
		if((n&1)==1){
			S=RollMul(S,A);
		}
		n>>=1;
		A=RollMul(A,A);
	}
	return S;
}
I L GetSum(int*v,Roll A){
	L res=0;
	int f[6];
	for(R i=0;i!=6;i++){
		res+=A.sum[i]*v[i];
		f[A.per[i]]=v[i];
	}
	for(R i=0;i!=6;i++){
		v[i]=f[i];
	}
	return res;
}
I L Calc(L a,L b,Roll A,Roll B,int*v){
	Roll S;
	if(a==1){
		S=RollPow(B,b-1);
		return GetSum(v,S);
	}
	S=RollPow(B,b/a);
	L res=GetSum(v,S);
	res+=Calc(b%a,a,B,RollMul(A,S),v);
	return res;
}
int main(){
	int v[6];
	L a,b;
	scanf("%llu%llu",&a,&b);
	for(R i=0;i!=6;i++){
		scanf("%d",v+i);
	}
	Roll A,B;
	const int liftP[6]={1,5,2,3,0,4},rightP[6]={0,2,4,1,3,5};
	const L liftV[6]={0,0,0,0,0,1},rightV[6]={0,0,1,0,0,0};
	A.Init(liftP,liftV);
	B.Init(rightP,rightV);
	if(a>b){
		L t=a;
		a=b;
		b=t;
	}else{
		Swap(v[0],v[3]);
		Swap(v[2],v[5]);
	}
	printf("%llu",v[4]+Calc(a,b,A,B,v));
	return 0;
}
计算几何

给定平面坐标范围 0 ∼ m 0 \sim m 0m,平面内有 n n n 个点坐标为 ( x i , y i ) (x_i,y_i) (xi,yi)。询问 q q q 次,每次给定一个点,求最大的正方形面积使得该正方形严格包含询问的点且不严格包含原有的点,若不存在则输出 0 0 0
t t t 组测试数据。
1 ⩽ t ⩽ 5 , 0 ⩽ n ⩽ 5000 , 1 ⩽ q ⩽ 5000 1 \leqslant t \leqslant 5,0 \leqslant n \leqslant 5000,1 \leqslant q \leqslant 5000 1t5,0n5000,1q5000
0 ⩽ x i , y i ⩽ m ⩽ 1 0 9 0 \leqslant x_i,y_i \leqslant m \leqslant 10^9 0xi,yim109
时间限制4s,空间限制512MB

首先将点排序。对于一个询问,加入点 ( 0 , 0 ) (0,0) (0,0) ( m , m ) (m,m) (m,m)。对于询问点,令双指针维护 x x x 方向上的点,可以预处理出 y y y 方向的前缀最值以获取 y y y 方向的长度。移动左端点,尝试移动右端点,使得 y y y 方向的长度小于目前 x x x 方向上的长度,最后更新答案即可。
注意要特判询问点与原有点重合以及在边界上的情况。
时间复杂度 O ( n q ) O(nq) O(nq),空间复杂度 O ( n ) O(n) O(n)

#include<stdio.h>
#include<set>
#include<algorithm>
using namespace std;
#define R register int
#define I inline
I int Max(const int x,const int y){
	return x>y?x:y;
}
I int Min(const int x,const int y){
	return x<y?x:y;
}
struct Point{
	int PosX,PosY;
	I void Read(){
		scanf("%d%d",&PosX,&PosY);
	}
	I friend bool operator<(Point A,Point B){
		if(A.PosX==B.PosX){
			return A.PosY<B.PosY;
		}
		return A.PosX<B.PosX;
	}
}p[5002];
I Point Pair(int x,int y){
	Point res;
	res.PosX=x;
	res.PosY=y;
	return res;
}
int minY[5002],maxY[5002],px[5002];
I int Calc(int&n,int&d,int x,int y){
	if(x==0||y==0||x==d||y==d){
		return 0;
	}
	int res=0,l=std::lower_bound(p,p+n+2,Pair(x,0))-p-1,r=n+1,yl=0,yr=d;
	minY[l+1]=d;
	maxY[l+1]=0;
	for(R i=l+1;i<=n;i++){
		minY[i+1]=minY[i];
		maxY[i+1]=maxY[i];
		if(p[i].PosY>=y){
			minY[i+1]=Min(minY[i+1],p[i].PosY);
		}
		if(p[i].PosY<=y){
			maxY[i+1]=Max(maxY[i+1],p[i].PosY);
		}
	}
	while(l!=-1){
		while(minY[r]==y||maxY[r]==y||p[r-1].PosX>x&&Min(yr,minY[r-1])-Max(yl,maxY[r-1])<p[r-1].PosX-p[l].PosX){
			r--;
		}
		res=Max(res,Min(Min(yr,minY[r])-Max(yl,maxY[r]),p[r].PosX-p[l].PosX));
		if(p[r-1].PosX>x){
			res=Max(res,Min(Min(yr,minY[r-1])-Max(yl,maxY[r-1]),p[r-1].PosX-p[l].PosX));
		}
		if(p[l].PosY>=y){
			yr=Min(yr,p[l].PosY);
		}
		if(p[l].PosY<=y){
			yl=Max(yl,p[l].PosY);
		}
		l--;
	}
	return res;
}
I void Solve(){
	int d,n,q,x,y,ans;
	scanf("%d%d",&d,&n);
	set<Point>S;
	for(R i=1;i<=n;i++){
		p[i].Read();
		S.insert(p[i]);
	}
	sort(p+1,p+n+1);
	p[n+1]=Pair(d,d);
	scanf("%d",&q);
	for(R i=0;i!=q;i++){
		scanf("%d%d",&x,&y);
		if(S.count(Pair(x,y))==1){
			puts("0");
		}else{
			ans=Calc(n,d,x,y);
			printf("%lld\n",(long long)ans*ans);
		}
	}
}
int main(){
	int t;
	scanf("%d",&t);
	for(R i=0;i!=t;i++){
		Solve();
	}
	return 0;
}

据说可以通过预处理的方式做到时间复杂度 O ( n 2 + q ) O(n^2+q) O(n2+q)

偶数图

给定一个简单无向图有 n n n 个点, m m m 条边,其中 m m m 为偶数。现在构造度数为奇数的点两两配对的方案,使得每对点之间的路径长度为偶数,且每条边在这些路径中总共只能出现一次,输出点的配对情况以及它们之间路径上的编号。
1 ⩽ n , m ⩽ 300000 1 \leqslant n,m \leqslant 300000 1n,m300000
时间限制1s,空间限制512MB

对于一个简单图边数为偶数,线图一定存在完美匹配。在图上找一颗DFS树出来,把一个点到儿子剩余的边以及发出的返祖边进行匹配,最后考虑到父亲节点的边,这样一定可以做到完美匹配。最后对于一个没被匹配的度数为奇数的点沿匹配好的一对边搜索直到找到另一个度数为奇数的点即可。显然这样一定有解。
时间复杂度 O ( m log ⁡ 2 m ) O(m \log_2m) O(mlog2m),空间复杂度 O ( n + m ) O(n+m) O(n+m)

#include<stdio.h>
#include<set>
#include<vector>
using namespace std;
#define R register int
#define I inline
#define N 300001
struct Edge{
	int End,Id;
};
I Edge Pair(int x,int y){
	Edge res;
	res.Id=x;
	res.End=y;
	return res;
}
vector<Edge>G[N];
bool vis[N],save[N];
struct PairEdge{
	int FirstId,SecondId,End;
	I friend bool operator<(PairEdge A,PairEdge B){
		return A.FirstId<B.FirstId;
	}
};
I PairEdge Link(int x,int y,int z){
	PairEdge res;
	res.FirstId=x;
	res.SecondId=y;
	res.End=z;
	return res;
}
set<PairEdge>E[N];
int dep[N];
I void Tarjan(int x,int F){
	vis[x]=true;
	dep[x]=dep[F]+1;
	int v,FId;
	for(vector<Edge>::iterator T=G[x].begin();T!=G[x].end();T++){
		v=T->End;
		if(v!=F){
			if(vis[v]==false){
				Tarjan(v,x);
			}
		}else{
			FId=T->Id;
		}
	}
	Edge cur;
	cur.Id=0;
	for(vector<Edge>::iterator T=G[x].begin();T!=G[x].end();T++){
		v=T->End;
		if(dep[v]==dep[x]+1&&save[v]==true||v!=F&&dep[v]<dep[x]){
			if(cur.Id==0){
				cur=*T;
			}else{
				E[cur.End].insert(Link(cur.Id,T->Id,v));
				E[v].insert(Link(T->Id,cur.Id,cur.End));
				cur.Id=0;
			}
		}
	}
	if(cur.Id==0){
		save[x]=true;
	}else{
		E[cur.End].insert(Link(cur.Id,FId,F));
		E[F].insert(Link(FId,cur.Id,cur.End));
	}
}
I int BFS(int x,vector<int>&S){
	PairEdge e=*E[x].begin();
	E[x].erase(E[x].begin());
	S.push_back(e.FirstId);
	S.push_back(e.SecondId);
	E[e.End].erase(Link(e.SecondId,e.FirstId,x));
	x=e.End;
	if(vis[x]==true){
		vis[x]=false;
		return x;
	}
	return BFS(x,S);
}
int main(){
	int n,m,x,y;
	scanf("%d%d",&n,&m);
	for(R i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		G[x].push_back(Pair(i,y));
		G[y].push_back(Pair(i,x));
	}
	Tarjan(1,0);
	for(R i=1;i<=n;i++){
		vis[i]=(G[i].size()&1)==1?true:false;
	}
	for(R i=1;i<=n;i++){
		if(vis[i]==true){
			vis[i]=false;
			vector<int>E;
			printf("%d %d",i,BFS(i,E));
			printf(" %d\n",E.size());
			for(vector<int>::iterator T=E.begin();T!=E.end();T++){
				printf("%d ",*T);
			}
			puts("");
		}
	}
	return 0;
}
矩阵的秩

求大小为 n n n 的01方阵在模 2 2 2意义下秩为 m m m 的数量对 1000000007 1000000007 1000000007取模。
t t t 组数据。
1 ⩽ t ⩽ 1 0 5 , 1 ⩽ m ⩽ n ⩽ 1 0 7 1 \leqslant t \leqslant 10^5,1 \leqslant m \leqslant n \leqslant 10^7 1t105,1mn107
时间限制1s,空间限制512MB

考虑一个大小为 m m m n n n 维向量之间有序的线性基的方案数,显然可以考虑依次加入线性基,则方案数为:
∏ i = 0 m − 1 ( 2 n − 2 i ) \prod_{i=0}^{m-1}(2^n-2^i) i=0m1(2n2i)
将公因式提出来得到方案数为:
2 m ( m − 1 ) 2 ∏ i = n − m + 1 n ( 2 i − 1 ) 2^{\frac{m(m-1)}{2}} \prod_{i=n-m+1}^n(2^i-1) 22m(m1)i=nm+1n(2i1)
对于剩下的 n − m n-m nm 行,可以考虑插入到这个线性基中。共 m + 1 m+1 m+1 段空位可选编号 0 ∼ m 0 \sim m 0m。这些向量要求与之前的向量线性相关,插在第 i i i 段的会使最后的方案翻 2 i 2^i 2i 倍。设生成函数 F ( x ) F(x) F(x)
F ( x ) = ∏ i = 0 m ∑ j = 0 ( 2 i x ) j = ∏ i = 0 m 1 1 − 2 i x F(x)=\prod_{i=0}^m \sum_{j=0}(2^ix)^j=\prod_{i=0}^m \frac{1}{1-2^ix} F(x)=i=0mj=0(2ix)j=i=0m12ix1
答案即为 [ x n − m ] F ( x ) [x^{n-m}]F(x) [xnm]F(x)。设 F ( x ) = ∑ i = 0 f i x i F(x)=\sum_{i=0}f_i x^i F(x)=i=0fixi,计算 f f f。这里带入 2 x 2x 2x 得到:
F ( 2 x ) = ∏ i = 1 m + 1 1 1 − 2 i x F(2x)=\prod_{i=1}^{m+1}\frac{1}{1-2^ix} F(2x)=i=1m+112ix1
于是得到方程:
F ( 2 x ) ( 1 − 2 m + 1 x ) = F ( x ) ( 1 − x ) F(2x)(1-2^{m+1}x)=F(x)(1-x) F(2x)(12m+1x)=F(x)(1x)
同时取第 i i i 项系数:
2 i f i − 2 m + i f i − 1 = f i − f i − 1 2^i f_i-2^{m+i}f_{i-1}=f_i-f_{i-1} 2ifi2m+ifi1=fifi1
解得:
f i = 2 m + i − 1 2 i − 1 f i − 1 f_i=\frac{2^{m+i}-1}{2^i-1}f_{i-1} fi=2i12m+i1fi1
因此结果为:
∏ i = m + 1 n ( 2 i − 1 ) ∏ i = 1 n − m ( 2 i − 1 ) \frac{\prod_{i=m+1}^n(2^i-1)}{\prod_{i=1}^{n-m}(2^i-1)} i=1nm(2i1)i=m+1n(2i1)
最终答案为:
2 m ( m − 1 ) 2 ∏ i = n − m + 1 n ( 2 i − 1 ) ∏ i = m + 1 n ( 2 i − 1 ) ∏ i = 1 n − m ( 2 i − 1 ) 2^{\frac{m(m-1)}{2}} \prod_{i=n-m+1}^n(2^i-1)\frac{\prod_{i=m+1}^n(2^i-1)}{\prod_{i=1}^{n-m}(2^i-1)} 22m(m1)i=nm+1n(2i1)i=1nm(2i1)i=m+1n(2i1)
显然预处理出 2 i − 1 2^i-1 2i1 的前缀积极其逆元即可快速得到答案。
时间复杂度 O ( n + t ) O(n+t) O(n+t),空间复杂度 O ( n ) O(n) O(n)

#include<stdio.h>
#define R register int
#define L long long
#define N 10000001
#define P 1000000007
int pw[N],fac[N],invf[N];
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;
}
inline void Solve(){
	int n,m;
	scanf("%d%d",&n,&m);
	printf("%d\n",(L)fac[n]*invf[m]%P*fac[n]%P*invf[n-m]%P*invf[n-m]%P*PowMod(2,((m-1ll)*m>>1)%(P-1))%P);
}
int main(){
	pw[0]=fac[0]=1;
	for(R i=1;i!=N;i++){
		pw[i]=pw[i-1]<<1;
		if(pw[i]>P){
			pw[i]-=P;
		}
		fac[i]=(pw[i]-1ll)*fac[i-1]%P;
	}
	invf[N-1]=PowMod(fac[N-1],P-2);
	for(R i=N-1;i!=0;i--){
		invf[i-1]=(pw[i]-1ll)*invf[i]%P;
	}
	int t;
	scanf("%d",&t);
	for(R i=0;i!=t;i++){
		Solve();
	}
	return 0;
}
第五题

给定一个长度为 n n n 的数列 a a a q q q 次询问区间 [ l , r ] [l,r] [l,r] 的最短子区间包含的数字与 [ l , r ] [l,r] [l,r] 包含的数字相同。
1 ⩽ n , q ⩽ 2000000 1 \leqslant n,q \leqslant 2000000 1n,q2000000
1 ⩽ a i ⩽ n 1 \leqslant a_i \leqslant n 1ain
时间限制5s,空间限制1GB

考虑定一个左端点 l ′ l' l,右端点 r ′ r' r,则一定满足在区间 [ l ′ + 1 , r ′ ] [l'+1,r'] [l+1,r] 不存在 a l ′ a_{l'} al。设 t i t_i ti 为在 i i i 之后值等于 a i a_i ai 最小的下标。那么显然右端点 r ′ ⩽ t l ′ r' \leqslant t_{l'} rtl,而且一定是最小的下标满足区间 [ l ′ , r ′ ] [l',r'] [l,r] 与区间 [ l ′ , t l ′ ] [l',t_{l'}] [l,tl] 的数字种类数相同。那么可以对每个 i i i 作为 l ′ l' l 更新答案。除此之外可能存在的情况就是在 l ′ l' l 之后 r r r 之前不存在 a l ′ a_{l'} al。在这种情况下, l ′ l' l 一定是在 r r r 之前的所有数中最后出现的最小下标。
到这一步,需要解决的问题就变成了定左端点找最优的右端点了,这可以用链表与线段树共同维护。
时间复杂度 O ( ( n + q ) log ⁡ 2 ( n + q ) ) O((n+q)\log_2(n+q)) O((n+q)log2(n+q)),空间复杂度 O ( n + q ) O(n+q) O(n+q)

#include<stdio.h>
#include<algorithm>
#include<vector>
using namespace std;
#define R register int
#define I inline
#define N 2000001
#define M 5000000
#define INF 9999999
I void Min(int&x,const int y){
	if(x>y){
		x=y;
	}
}
int a[N],Next[N],ans[N],val[M],back[N],Last[N],suf[M],pre[M];
I void Add(int*s,int p,int lf,int rt,const int x){
	s[p]++;
	if(lf!=rt){
		int mid=lf+rt>>1;
		if(x>mid){
			Add(s,p<<1|1,mid+1,rt,x);
		}else{
			Add(s,p<<1,lf,mid,x);
		}
	}
}
I int GetSum(int*s,int p,int lf,int rt,const int l,const int r){
	if(l<=lf&&rt<=r){
		return s[p];
	}
	int mid=lf+rt>>1,res=0;
	if(l<=mid){
		res=GetSum(s,p<<1,lf,mid,l,r);
	}
	if(r>mid){
		res+=GetSum(s,p<<1|1,mid+1,rt,l,r);
	}
	return res;
}
I int FindSuf(int p,int lf,int rt,int x){
	if(lf==rt){
		return rt;
	}
	if(suf[p<<1|1]<x){
		return FindSuf(p<<1,lf,lf+rt>>1,x-suf[p<<1|1]);
	}
	return FindSuf(p<<1|1,lf+rt+2>>1,rt,x);
}
I int FindPre(int p,int lf,int rt,int x){
	if(lf==rt){
		return lf;
	}
	if(pre[p<<1]<x){
		return FindPre(p<<1|1,lf+rt+2>>1,rt,x-pre[p<<1]);
	}
	return FindPre(p<<1,lf,lf+rt>>1,x);
}
I void Init(int p,int lf,int rt){
	val[p]=INF;
	if(lf!=rt){
		Init(p<<1,lf,lf+rt>>1);
		Init(p<<1|1,lf+rt+2>>1,rt);
	}
}
I void Modify(int p,int lf,int rt,const int x,const int d){
	if(lf!=rt){
		int mid=lf+rt>>1;
		if(x>mid){
			Modify(p<<1|1,mid+1,rt,x,d);
		}else{
			Modify(p<<1,lf,mid,x,d);
		}
		val[p]=val[p<<1];
		Min(val[p],val[p<<1|1]);
	}else{
		val[p]=d;
	}
}
I int GetMin(int p,int lf,int rt,const int l,const int r){
	if(l<=lf&&rt<=r){
		return val[p];
	}
	int mid=lf+rt>>1,res=INF;
	if(l<=mid){
		res=GetMin(p<<1,lf,mid,l,r);
	}
	if(r>mid){
		Min(res,GetMin(p<<1|1,mid+1,rt,l,r));
	}
	return res;
}
struct Query{
	int Lf,Rt,Id,Pos,Totcol;
}q[N];
I bool CompareRt(Query A,Query B){
	return A.Rt>B.Rt;
}
I bool ComparePos(Query A,Query B){
	return A.Pos<B.Pos;
}
I bool CompareCol(Query A,Query B){
	return A.Totcol<B.Totcol;
}
struct Segment{
	int Lf,Col,Len;
	I friend bool operator<(Segment A,Segment B){
		return A.Col<B.Col;
	}
};
I Segment Pair(int x,int y,int z){
	Segment res;
	res.Lf=x;
	res.Col=y;
	res.Len=z;
	return res;
}
int main(){
	int n,m,r;
	scanf("%d",&n);
	for(R i=1;i<=n;i++){
		Last[i]=-1;
		scanf("%d",a+i);
	}
	for(R i=n;i!=0;i--){
		Next[i]=Last[a[i]];
		Last[a[i]]=i;
	}
	for(R i=1;i<=n;i++){
		if(Last[i]!=-1){
			Add(pre,1,1,n,Last[i]);
			Last[i]=-1;
		}
	}
	for(R i=1;i<=n;i++){
		back[i]=Last[a[i]];
		Last[a[i]]=i;
	}
	for(R i=1;i<=n;i++){
		if(Last[i]!=-1){
			Add(suf,1,1,n,Last[i]);
		}
	}
	scanf("%d",&m);
	for(R i=0;i!=m;i++){
		scanf("%d%d",&q[i].Lf,&q[i].Rt);
		q[i].Id=i;
		ans[i]=INF;
	}
	sort(q,q+m,CompareRt);
	r=n;
	for(R i=0;i!=m;i++){
		while(r>q[i].Rt){
			if(back[r]!=-1){
				Add(suf,1,1,n,back[r]);
			}
			r--;
		}
		q[i].Pos=FindSuf(1,1,n,GetSum(suf,1,1,n,q[i].Lf,n));
		q[i].Totcol=GetSum(suf,1,1,n,q[i].Lf,q[i].Rt);
	}
	sort(q,q+m,ComparePos);
	r=0;
	Init(1,1,n);
	vector<Segment>S;
	for(R i=1;i<=n;i++){
		while(r!=m&&q[r].Pos==i){
			Min(ans[q[r].Id],FindPre(1,1,n,GetSum(pre,1,1,n,1,q[r].Rt))-i+1);
			r++;
		}
		if(Next[i]!=-1){
			S.push_back(Pair(i,GetSum(pre,1,1,n,i,Next[i]),FindPre(1,1,n,GetSum(pre,1,1,n,1,Next[i]))-i+1));
			Add(pre,1,1,n,Next[i]);
		}
	}
	sort(S.begin(),S.end());
	sort(q,q+m,CompareCol);
	r=0;
	vector<Segment>::iterator T=S.begin(),T2;
	T2=T;
	while(T!=S.end()){
		while(T2!=S.end()&&T2->Col==T->Col){
			Modify(1,1,n,T2->Lf,T2->Len);
			T2++;
		}
		while(r!=m&&q[r].Totcol<=T->Col){
			if(q[r].Totcol==T->Col&&q[r].Pos!=q[r].Lf){
				Min(ans[q[r].Id],GetMin(1,1,n,q[r].Lf,q[r].Pos-1));
			}
			r++;
		}
		while(T!=T2){
			Modify(1,1,n,T->Lf,INF);
			T++;
		}
	}
	for(R i=0;i!=m;i++){
		printf("%d\n",ans[i]);
	}
	return 0;
}
猜数字

n n n 个数字,每个数字都是 − 1 , 0 , 1 -1,0,1 1,0,1 中的一个,其中至少有一个 1 1 1和一个 − 1 -1 1。有两种向交互库的询问求出这 n n n 个数:

  1. 询问两个不交的下标集合 S S S T T T,交互库会返回 S S S 中的数之和乘以 T T T 中的数之和的正负性,则返回值只会有 − 1 , 0 , 1 -1,0,1 1,0,1
  2. 询问一个数组是不是那 n n n 个数字。

第一种询问最多问 1 0 6 10^6 106次,询问的集合总大小不超过 1 0 7 10^7 107,第二种询问最多问一次。
使用头文件guess.h
1 ⩽ n ⩽ 1 0 5 1 \leqslant n \leqslant 10^5 1n105
时间限制1s,空间限制512MB

当确定一个点不为 0 0 0时,可以直接问出所以数与其的关系,然后用一次询问二即可。
考虑分治求出这个数。若已知区间 [ l , r ] [l,r] [l,r] 一定存在多余一个的非零的数,则不断二分到两个子区间的和乘积为 0 0 0。这可以通过不断二分搜索求出。这样一定可以求得出来,可以通过归纳证明出来。
求出来之后两个区间一定分别有不为零的数,这样就可以对一个区间二分与另一个区间询问即可找到要求的位置。询问一次数为 O ( n ) O(n) O(n),集合总大小为 O ( n log ⁡ 2 n ) O(n \log_2n) O(nlog2n)
时间复杂度 O ( n log ⁡ 2 n ) O(n \log_2n) O(nlog2n),空间复杂度 O ( n ) O(n) O(n)

#include<stdio.h>
#include"guess.h"
using namespace std;
#define I inline
I void MakeVector(int l,int r,vector<int>&A){
	for(int i=l;i<=r;i++){
		A.push_back(i);
	}
}
I pair<int,int>FindNode(int l,int r){
	if(l==r){
		return make_pair(-1,0);
	}
	int mid=l+r>>1,x;
	vector<int>A,B;
	MakeVector(l,mid,A);
	MakeVector(mid+1,r,B);
	x=ask1(A,B);
	if(x!=0){
		return make_pair(l,r);
	}
	pair<int,int>P=FindNode(l,mid);
	if(P.first==-1){
		return FindNode(mid+1,r);
	}
	return P;
}
I int FindOne(int l,int r){
	int mid=l+r>>1;
	vector<int>B;
	MakeVector(mid+1,r,B);
	r=mid;
	while(l!=r){
		mid=l+r>>1;
		vector<int>A;
		MakeVector(mid+1,r,A);
		if(ask1(A,B)!=0){
			l=mid+1;
		}else{
			r=mid;
		}
	}
	return l;
}
vector<int>solve(int n,int x,int y){
	pair<int,int>S=FindNode(0,n-1);
	x=FindOne(S.first,S.second);
	vector<int>B,C;
	MakeVector(x,x,B);
	for(register int i=0;i!=n;i++){
		if(i!=x){
			vector<int>A;
			MakeVector(i,i,A);
			C.push_back(ask1(A,B));
		}else{
			C.push_back(1);
		}
	}
	if(ask2(C)==false){
		for(vector<int>::iterator T=C.begin();T!=C.end();T++){
			*T=-*T;
		}
	}
	return C;
}
字符串

给定一个字符串长度为 n n n 以及一个大小为 n n n 的数组 a a a,设 s i s_i si 为该串以第 i i i 个位置为开头的后缀。给 q q q 次询问,每次询问区间 [ l , r ] [l,r] [l,r] 中有多少对 i , j i,j i,j 满足 l ⩽ i , j ⩽ r l \leqslant i,j \leqslant r li,jr s i , s j s_i,s_j si,sj 最长公共前缀大小不小于 a i a_i ai
1 ⩽ l ⩽ r ⩽ n 1 \leqslant l \leqslant r \leqslant n 1lrn
1 ⩽ q ⩽ 1 0 5 1 \leqslant q \leqslant 10^5 1q105
时间限制2s,空间限制512MB

对于 i = j i=j i=j 的情况很简单,求一个前缀和即可。
对于 i ≠ j i \neq j i=j 的情况,对原串求后缀数组,那么 s i , s j s_i,s_j si,sj 的最长共前缀即为 r a n k rank rank 数组之间 h e i g h t height height 的最小值。对于每个后缀用ST表等可以算出它作为 s i s_i si,另外哪个区间的后缀能作为后缀。这是个二维区间问题,考虑莫队算法。加入一个后缀或删除一个后缀可以使用树状数组查询以及修改,时间复杂度 O ( n n log ⁡ 2 n ) O(n\sqrt n \log_2n) O(nn log2n)
由于贡献可减,因此莫队可以使用二次离线优化。
时间复杂度 O ( ( n + q ) n + q log ⁡ 2 q ) O((n+q)\sqrt n+q \log_2q) O((n+q)n +qlog2q),空间复杂度 O ( n log ⁡ 2 n + q ) O(n \log_2n+q) O(nlog2n+q)

#include<stdio.h>
#include<algorithm>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define I inline
#define S 317
#define N 100002
I int Min(const int x,const int y){
	return x<y?x:y;
}
char s[N];
int hei[N],sum[N],pre[N],suf[N],rk[N],sa[N],lf[N],rt[N],a[N],g[2][17][N];
I void GetSA(int&n){
	static int ct[N],sb[N],tem[N];
	int k=26;
	int*rk1=rk,*tem2=tem;
	for(R i=1;i<=n;i++){
		ct[s[i]-96]++;
	}
	for(R i=1;i<=k;i++){
		ct[i]+=ct[i-1];
	}
	for(R i=n;i!=0;i--){
		sa[ct[s[i]-96]]=i;
		ct[s[i]-96]--;
	}
	for(R i=1;i<=k;i++){
		ct[i]=0;
	}
	k=0;
	for(R i=1;i<=n;i++){
		if(s[sa[i]]!=s[sa[i-1]]){
			k++;
		}
		rk[sa[i]]=k;
	}
	for(R m=1;m<n&&k!=n;m<<=1){
		for(R i=1;i<=m;i++){
			sb[i]=n-m+i;
		}
		int t=m+1;
		for(R i=1;i<=n;i++){
			if(sa[i]>m){
				sb[t]=sa[i]-m;
				t++;
			}
		}
		for(R i=1;i<=n;i++){
			ct[rk1[i]]++;
		}
		for(R i=1;i<=k;i++){
			ct[i]+=ct[i-1];
		}
		for(R i=n;i!=0;i--){
			sa[ct[rk1[sb[i]]]]=sb[i];
			ct[rk1[sb[i]]]--;
		}
		for(R i=1;i<=k;i++){
			ct[i]=0;
		}
		k=0;
		for(R i=1;i<=n;i++){
			if(rk1[sa[i]]!=rk1[sa[i-1]]||rk1[sa[i]+m]!=rk1[sa[i-1]+m]){
				k++;
			}
			tem2[sa[i]]=k;
		}
		int*b=rk1;
		rk1=tem2;
		tem2=b;
	}
	if(rk1!=rk){
		for(R i=1;i<=n;i++){
			rk[i]=tem[i];
		}
	}
	k=0;
	for(R i=1;i<=n;i++){
		if(k!=0){
			k--;
		}
		while(s[i+k]==s[sa[rk[i]-1]+k]){
			k++;
		}
		hei[rk[i]]=k;
	}
}
struct Query{
	int Lf,Rt,Id;
	I friend bool operator<(Query A,Query B){
		int x=A.Lf/S;
		if(x!=B.Lf/S){
			return A.Lf<B.Lf;
		}
		return(x&1)==1?A.Rt>B.Rt:A.Rt<B.Rt;
	}
}q[N];
L ans[N],mlf[N],mrt[N];
class Blocker{
	int a[S],b[S][S];
	I int GetPre(int r){
		int bl=r/S;
		return(bl==0?0:a[bl-1])+b[bl][r%S];
	}
	public:
		I void Clear(){
			for(R i=0;i!=S;i++){
				a[i]=0;
				for(R j=0;j!=S;j++){
					b[i][j]=0;
				}
			}
		}
		I void Add(int x,const int d){
			int bl=x/S;
			for(R i=x%S;i!=S;i++){
				b[bl][i]+=d;
			}
			for(R i=bl;i!=S;i++){
				a[i]+=d;
			}
		}
		I int GetSum(int l,int r){
			return GetPre(r)-GetPre(l-1);
		}
}B1,B2;
I int Calc(int x){
	return B2.GetSum(1,rk[x])+B1.GetSum(lf[x],rt[x]);
}
I void Insert(int x){
	B1.Add(rk[x],1);
	B2.Add(lf[x],1);
	B2.Add(rt[x]+1,-1);
}
struct Item{
	int Id,Lf,Rt;
	bool type;
};
I Item Pair(int x,int l,int r,bool t){
	Item res;
	res.Id=x;
	res.Lf=l;
	res.Rt=r;
	res.type=t;
	return res;
}
vector<Item>G[N],H[N];
int main(){
	int n,m,curl=1,curr=0,x;
	scanf("%d%d",&n,&m);
	scanf("%s",s+1);
	GetSA(n);
	for(R i=2;i<=n;i++){
		g[0][0][i]=hei[i];
	}
	for(R i=1;i!=n;i++){
		g[1][0][i]=hei[i+1];
	}
	for(R i=1;i<=n;i++){
		scanf("%d",a+i);
		sum[i]=sum[i-1]+(n-i+2>a[i]);
	}
	for(R i=1;i!=17;i++){
		for(R j=1<<i|1;j<=n;j++){
			g[0][i][j]=Min(g[0][i-1][j],g[0][i-1][j-(1<<i-1)]);
		}
		for(R j=n-(1<<i);j>0;j--){
			g[1][i][j]=Min(g[1][i-1][j],g[1][i-1][j+(1<<i-1)]);
		}
	}
	for(R i=1;i<=n;i++){
		x=i;
		for(R j=16;j!=-1;j--){
			if(x>1<<j&&g[0][j][x]>=a[sa[i]]){
				x-=1<<j;
			}
		}
		lf[sa[i]]=x;
		x=i;
		for(R j=16;j!=-1;j--){
			if(x+(1<<j)<=n&&g[1][j][x]>=a[sa[i]]){
				x+=1<<j;
			}
		}
		rt[sa[i]]=x;
	}
	for(R i=1;i<=n;i++){
		pre[i]=Calc(i);
		Insert(i);
	}
	B1.Clear();
	B2.Clear();
	for(R i=n;i!=0;i--){
		suf[i]=Calc(i);
		Insert(i);
	}
	B1.Clear();
	B2.Clear();
	for(R i=0;i!=m;i++){
		scanf("%d%d",&q[i].Lf,&q[i].Rt);
		q[i].Id=i;
	}
	sort(q,q+m);
	for(R i=0;i!=m;i++){
		if(curr<q[i].Rt){
			if(curl!=1){
				G[curl-1].push_back(Pair(i,curr+1,q[i].Rt,true));
			}
			curr=q[i].Rt;
		}
		if(curl>q[i].Lf){
			if(curr!=n){
				H[curr+1].push_back(Pair(i,q[i].Lf,curl-1,true));
			}
			curl=q[i].Lf;
		}
		if(curr>q[i].Rt){
			if(curl!=1){
				G[curl-1].push_back(Pair(i,q[i].Rt+1,curr,false));
			}
			curr=q[i].Rt;
		}
		if(curl<q[i].Lf){
			if(curr!=n){
				H[curr+1].push_back(Pair(i,curl,q[i].Lf-1,false));
			}
			curl=q[i].Lf;
		}
	}
	L cur;
	for(R i=1;i<=n;i++){
		Insert(i);
		for(vector<Item>::iterator T=G[i].begin();T!=G[i].end();T++){
			cur=0;
			for(R j=T->Lf;j<=T->Rt;j++){
				cur+=Calc(j);
			}
			mrt[T->Id]=T->type==true?-cur:cur;
		}
	}
	B1.Clear();
	B2.Clear();
	for(R i=n;i!=0;i--){
		Insert(i);
		for(vector<Item>::iterator T=H[i].begin();T!=H[i].end();T++){
			cur=0;
			for(R j=T->Lf;j<=T->Rt;j++){
				cur+=Calc(j);
			}
			mlf[T->Id]=T->type==true?-cur:cur;
		}
	}
	cur=curr=0;
	curl=1;
	for(R i=0;i!=m;i++){
		cur+=mrt[i]+mlf[i];
		while(curr<q[i].Rt){
			curr++;
			cur+=pre[curr];
		}
		while(curl>q[i].Lf){
			curl--;
			cur+=suf[curl];
		}
		while(curr>q[i].Rt){
			cur-=pre[curr];
			curr--;
		}
		while(curl<q[i].Lf){
			cur-=suf[curl];
			curl++;
		}
		ans[q[i].Id]=cur+sum[q[i].Rt]-sum[q[i].Lf-1];
	}
	for(R i=0;i!=m;i++){
		printf("%lld\n",ans[i]);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值