【纪中集训】2019.08.02【NOIP提高组】模拟 A 组TJ

( ewcommand{RNum}[1]{uppercaseexpandafter{ omannumeral #1 elax}})

T1

  • 一道可以暴力撵标算的题……

Description

  • 给定二维平面上(N(≤60000))个有权值的点((X_i,Y_i)(in[0,10^9])),点权(Z_iin[0,10^9])
  • (M(≤10000))次操作,操作(Ⅰ)是询问一个边平行于坐标轴的矩形中权值第(K)小的点,操作(Ⅱ)是交换两个点的权值。
  • 时限10s。

SolutionⅠ

  • 既然时限10s,那肯定要想到暴力。
  • 最朴素暴力应该是找出所有在矩形中的点,快排,取第(K)小。这样是(O(NMlog_2N))的,实测会(TLE),只有52.4pts。
  • 考虑(O(N))找第(K)小。可以先将权值离散化,然后把快排改为桶牌,这样就变成(O(Nlog_2N+NM))了。直接交似乎是3~4s;吸臭氧后可以达到1s+。
Code
#include <cstdio>
#include <algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define O3 __attribute((optimize("-O3")))
#define NO {puts("It doesn't exist."); continue;}
using namespace std;

const int N=81e3;
int i,j,n,m,n1,Z[N],pos[N],x,y,x1,y1,k,l,r,mid,cnt[N],ss;
struct city{int x,y,z,i;}a[N];

O3 void read(int&x)
{
	char c=getchar(); x=0;
	for(;c<48|c>57;c=getchar());
	for(;c>47&c<58;c=getchar()) x=x*10+(c^48);
}

O3 bool cmpx(city a,city b) {return a.x<b.x;}

O3 bool cmpz(int x,int y) {return a[x].z<a[y].z;}

O3 int main()
{
	read(n),read(m);
	fo(i,1,n) read(a[a[i].i=i].x),read(a[i].y),read(a[i].z),Z[i]=a[i].z;
	sort(Z+1,Z+n+1), n1=unique(Z+1,Z+n+1)-Z-1;
	sort(a+1,a+n+1,cmpx);
	fo(i,1,n) pos[a[i].i]=i, a[i].z=lower_bound(Z+1,Z+n1+1,a[i].z)-Z;
	while(m--)
	{
		char c=getchar();
		while(c!='Q'&c!='S') c=getchar();
		read(x),read(y);
		if(c=='Q')
		{
			read(x1),read(y1),read(k);
			if(x>x1) swap(x,x1);
			if(y>y1) swap(y,y1);
			l=1,r=n;
			while(l<r)
			{
				mid=l+r>>1;
				a[mid].x>=x?r=mid:l=mid+1;
			}
			if(n-l+1<k) NO
			fo(i,0,n1) cnt[i]=0;
			fo(i,l,n)
			{
				if(a[i].x>x1) break;
				if(y<=a[i].y&a[i].y<=y1) cnt[0]++,cnt[a[i].z]++;
			}
			if(cnt[0]<k) NO
			fo(i,1,n1)
			{
				if(cnt[i]>=k) break;
				k-=cnt[i];
			}
			printf("%d
",Z[i]);
		}
		else swap(a[pos[x+1]].z,a[pos[y+1]].z);
	}
}

SolutionⅡ

  • 这题实际上是李超12年出的,他当时的做法是分块+远古数据结构划分树。
  • 我们可以将点按(x)坐标分(sqrt N)块,同一块内按(y)坐标排序并构建划分树;这样一来,对于一个询问,它至多覆盖了(sqrt N)个整块和2个散块。我们可以同时处理所有整块,就在划分树上跑;散块则暴力处理。
  • 这样一来,我们就可以达到(O(N^{1.5}+MN^{0.5}log_2N^{0.5}))效率。
  • 代码复杂度翻若干倍。

SolutionⅢ

  • 既然是求第(K)小,自然可以想到整体二分……
  • 类比一下求区间第(K)小的做法:我们先离线,把所有操作放到一起进行整体二分答案,这就变成了一个一维偏序问题;那这题也可以类似地转化为整体二分+二维偏序。再打一个带修主席树!
  • 时间复杂度达到了惊人的(O((N+M)log_2^3 N))
  • 代码复杂度翻若干倍。

T2

Description

  • 有个好(song)玩(ming)的游戏,有(N(≤10^8))个关卡,初始有(Q(≤5))条命。
  • 每通过一个关卡,会得到(u)分和1条命,生命上限为(Q)。其中(u=min()最近一次连续通过的关数(,R))
  • 设通过一关的概率为(p(in[0,1])),求最小的(p)使得期望得分超过(S)

Solution

  • 我原本以为如果(p)很小,算到后面会爆精度,需要什么奇技淫巧……
  • 结果不需要考虑这些问题。

  • 首先,我们可以预处理出所有可能的状态(当前的(HP)与当前的(u))。极限数据时也就36种。
  • 可以想到二分答案+(DP)判可行:设(f[i][s])表示当前过了第(i)关,状态为(s)的概率。这样的话,我们要一边转移一边更新答案,所以可以把答案设为状态0加入转移。
  • 但是(N)太大,所以矩乘优化。
  • 设二分次数为(L),状态数为(S),则时间复杂度:(O(LS^3log_2N))

Code

#include <cstdio>
#include <cstring>
#define min(x,y) (x<y?x:y)
#define S(a) memset(a,0,sizeof a);
#define C(a,b) memcpy(a,b,sizeof a);
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef double db;

const int S=37;
int i,j,k,x,y,n,R,Q,s,rain,g[S][S],l,r,mid,tmp;
db p,t[S][S],t1[S][S],a[S],a1[S];

void work()
{
	S(t) S(a)
	t[0][0]=a[g[Q][0]]=1;
	fo(i,1,Q)
		fo(j,0,R)
			if(x=g[i][j])
			{
				t[0][x]=p*min(j+1,R);
				if(y=g[min(i+1,Q)][min(j+1,R)]) t[y][x]=p;
				t[g[i-1][0]][x]=1-p;
			}
	for(tmp=n; tmp; tmp>>=1)
	{
		if(tmp&1)
		{
			C(a1,a)
			fo(i,0,rain)
			{
				a[i]=0;
				fo(j,0,rain) a[i]+=t[i][j]*a1[j];
			}
		}
		C(t1,t)
		fo(i,0,rain) fo(j,0,rain)
		{
			t[i][j]=0;
			fo(k,0,rain) t[i][j]+=t1[i][k]*t1[k][j];
		}
	}
}

int main()
{
	scanf("%d%d%d%d",&n,&R,&Q,&s);
	fo(i,0,Q) 
		fo(j,0,R) 
			if(i>=j||i==Q) g[i][j]=++rain;
	l=0, r=1e7;
	while(l<r)
	{
		mid=l+r>>1;
		p=mid/1e7;
		work();
		a[0]>s ? r=mid : l=mid+1;
	}
	p=l/1e7;
	work();
	a[0]>s ? printf("%.6lf",p) : puts("Impossible.");
}

T3

Description

  • 给定二维平面上(N(≤10^5))个点((X_i,Y_i)(in[0,10^8])),选3个点,求它们两两之间的曼哈顿距离和的最大值和最小值。

Solution

  • 3个点的曼哈顿距离和实际上是(2*(X_{max}-X_{min}+Y_{max}-Y_{min})),即3点的外接矩形的边长。那么1个点可以确定其中1~2项。
  • 最大值其实很好求。因为对于一个点而言,它可以确定的总共有8种。我们求出这8种的最大值,配对一下即可。而由于是找最大值,所以在(N>3)的时候,一定不会算重复点。
  • 难点就在求最小值了。

  • 我们可以将其划分为两种情况:情况Ⅰ是1个点确定了2项,剩下2个点各确定1项;情况Ⅱ是2个点确定2项,剩下1个点什么也没确定。
  • 情况Ⅰ又可细分为4种小情况,这里只考虑1个点确定右上角(即该点为((X_{max},Y_{max})))的情况(剩下的可以通过旋转转化而来)。那么还有剩下2个点分别是((X_{min},Y))((Y,X_{min}))
  • 我们按(x)坐标对点排序,并以(x)轴建立线段树,维护三个值:(X_{min},Y_{min},min(X_{min}+Y_{min}))。扫描线,当扫到一个点((X_i,Y_i))时,先查询它的答案,再用(Y_i)区间修改,最后用(X_i)单点修改。注意区间修改时,如果某区间没有点则不修改;并且只有区间修改时才更新答案。这样就不会自己影响到自己。
  • 情况Ⅱ也可细分为2种小情况,也是可以旋转转化。这个比较简单,在此不讲了。

  • 总时间复杂度:(O(Nlog_2N))

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define A v*2
#define B A+1
#define L A,l,m
#define R B,m+1,r
#define MAX(x,y) if(x<y)x=y
#define fo(i,a,b) for(i=a;i<=b;i++)
#define MS(a,x) memset(a,x,sizeof a);
using namespace std;

const int N=11e4,S=N<<3,inf=0x33333333;
int i,n,x,y,nX,X[N],nY,Y[N],a1,a2,a3,a4,a5,a6,a7,a8,tx[S],ty[S],tag[S],t[S],mt[S];
bool tv[S];
struct rain{int x,y;}a[N];

inline int min(const int&x,const int&y) {return x<y?x:y;}
inline int max(const int&x,const int&y) {return x>y?x:y;}

inline bool cmp(const rain&a,const rain&b) {return a.x<b.x|a.x==b.x&a.y<b.y;}

inline void MIN(int&a,const int&b) {if(a>b)a=b;}

inline void New(const int&v) {if(!tv[v]) tv[v]=1;}

inline void update(const int&v)
{
	if( tv[A]&!tv[B]) tx[v]=tx[A], ty[v]=ty[A], t[v]=t[A], mt[v]=mt[A];
	else
	if(!tv[A]& tv[B]) tx[v]=tx[B], ty[v]=ty[B], t[v]=t[B], mt[v]=mt[B];
	else
	tx[v]=min(tx[A],tx[B]), ty[v]=min(ty[A],ty[B]), t[v]=min(t[A],t[B]), mt[v]=max(mt[A],mt[B]);
}

inline void mask(const int&v,const int&y) 
{
	if(mt[v]>tx[v]+y) mt[v]=tx[v]+(ty[v]=tag[v]=y), MIN(t[v],mt[v]);
}

inline void push(const int&v)
{
	if(tag[v]==inf) return;
	New(A), mask(A,tag[v]);
	New(B), mask(B,tag[v]);
	tag[v]=inf;
}

int que(int v,int l,int r)
{
	if(!tv[v]|tx[v]==inf|ty[v]==inf|a[i].y<l) return inf;
	if(a[i].y>=r) return t[v];
	push(v);
	int m=l+r>>1;
	return min(que(L),que(R));
}

void mdf(int v,int l,int r)
{
	if(!tv[v]|a[i].y>r) return;
	if(a[i].y<=l) {mask(v,-y); return;}
	push(v);
	int m=l+r>>1;
	mdf(L), mdf(R);
	update(v);
}
 
void add(int v,int l,int r)
{
	New(v);
	if(l==r) {tx[v]=-x; return;}
	push(v);
	int m=l+r>>1;
	a[i].y<=m ? add(L) : add(R);
	update(v);
}

void work1()
{
	sort(a+1,a+n+1,cmp);
	MS(tv,0) MS(tx,51) MS(ty,51) MS(tag,51) MS(t,51) MS(mt,50)
	fo(i,1,n)
	{
		x=X[a[i].x], y=Y[a[i].y];
		MIN(a1,2*(que(1,1,nY)+x+y));
		mdf(1,1,nY);
		add(1,1,nY);
	}
}

int Que(int v,int l,int r,bool k)
{
	if(!tv[v]|a[i].y<l) return inf;
	if(a[i].y>=r) return k?t[v]:tx[v];
	int m=l+r>>1;
	return min(Que(L,k),Que(R,k));
}

void Mdf(int v,int l,int r,int pv,bool k)
{
	New(v);
	if(l==r) {k?MIN(t[v],pv):MIN(tx[v],pv); return;}
	int m=l+r>>1;
	a[i].y<=m ? Mdf(L,pv,k) : Mdf(R,pv,k);
	tx[v]=min(tx[A],tx[B]), t[v]=min(t[A],t[B]);
}

void work2()
{
	MS(tv,0) MS(tx,51) MS(t,51)
	fo(i,1,n)
	{
		x=X[a[i].x], y=Y[a[i].y];
		MIN(a1,2*(Que(1,1,nY,1)+x+y));
		Mdf(1,1,nY,Que(1,1,nY,0),1);
		Mdf(1,1,nY,-x-y,0);
	}
}

void rotate() 
{
	fo(i,1,n) a[i]={-Y[a[i].y],X[a[i].x]};
	fo(i,1,n) X[i]=a[i].x, Y[i]=a[i].y;
	sort(X+1,X+n+1), nX=unique(X+1,X+n+1)-X-1;
	sort(Y+1,Y+n+1), nY=unique(Y+1,Y+n+1)-Y-1;
	fo(i,1,n) a[i]={lower_bound(X+1,X+nX+1,a[i].x)-X,lower_bound(Y+1,Y+nY+1,a[i].y)-Y};	
}

int main()
{
	scanf("%d",&n);

	a1=a2=a3=a4=a5=a6=a7=a8=-inf;
	fo(i,1,n)
	{
		scanf("%d%d",&X[i],&Y[i]), a[i]={X[i],Y[i]};
		MAX(a1, X[i]+Y[i]);
		MAX(a2,-X[i]+Y[i]);
		MAX(a3,-X[i]-Y[i]);
		MAX(a4, X[i]-Y[i]);
		MAX(a5,-X[i]); MAX(a6,X[i]);
		MAX(a7,-Y[i]); MAX(a8,Y[i]);
	}
	a1=2*max(max(a1+a3,a2+a4),max(max(a1+a5+a7,a2+a6+a7),max(a3+a6+a8,a4+a5+a8)));
	printf("%d
",a1);
	
	a1=inf;
	sort(X+1,X+n+1), nX=unique(X+1,X+n+1)-X-1;
	sort(Y+1,Y+n+1), nY=unique(Y+1,Y+n+1)-Y-1;
	fo(i,1,n) a[i]={lower_bound(X+1,X+nX+1,a[i].x)-X,lower_bound(Y+1,Y+nY+1,a[i].y)-Y};
	work1(), work2(), rotate();
	work1(), work2(), rotate();
	work1(), rotate(), work1();
	printf("%d",a1);
}
原文地址:https://www.cnblogs.com/Iking123/p/11294139.html