[游记&题解]2019暑假中山纪中集训day2

中山纪中NOIP2019提高组模拟赛

A组 day2 solution

Date:2019.8.2

Probelm A B C
题目名称 Attack Contra Bomb
Difficulty 1600/2800 2400 2000/3000
Finished Yes Yes Yes

简单游记

day 2

AM
今天是自闭的第二天。
吸取了前一天的教训,今天总算来的早了一些
然后就看到几个大字**[集训队互测]**!!!
感觉今天的题多半不可做。
然后看A…二维矩阵第k小值带修改模板题???而且只有一个subtask???
一看就是不可做题…直接跳…
然后看B题,显然要二分,然后就是一个dp题了…好像要用矩阵乘法优化??
推了半天,发现得分和生命都是一个分段函数…
然后发现dp是四维的…subtask2都过不了…
然后看C题,看到题目感觉题目比较简单…
分析一下,发现最大值直接保存 x x x, y y y, x + y x+y x+y, x − y x-y xy最大最小的8个点然后暴力即可…
用同样的思路去分析最小值,发现行不通。
然后想到了另外一种思路…可以先枚举 x x x最大的是哪一个,然后分类讨论它是 x + y x+y x+y x − y x-y xy或者都不是的时候的情况,用线段树维护前面这些点的最接近当前 y y y坐标的点,再分类讨论这些点是作为哪个值最接近。
然后发现要用两颗线段树维护,而且代码很难打,考虑到前面的题目好像都很不可做,于是就尝试的打了一下,打了接近100行就发现了一堆bug…
于是就放弃了,感觉自己今天又要爆零了…
然后又回去看B题,发现自己把题目给看错了…
然后dp就是三维的了,赶紧把subtask2写了,调了好久才调出来…
然后时间就差不多到了…也没有时间写B的矩阵乘法了…
然后就开始自闭…
PM
下午来看题解惊讶的发现A题暴力可以直接AC…[据说是搬题目的人手残多打了一个0…]。
然后T2是一个矩阵快速幂…
T3果然是两个线段树分类讨论的题目…然而被其它大佬用随机化分治给乱搞过去了…

Attack (attack)

[题解]

算法标签:暴力/整体二分+树套树
因为是集训队互测的题,真没想到暴力可以水过…

暴力做法就是先将点按val排序,然后暴力找到满足条件的第 k k k个即可,注意修改时需要使用指针。

整体二分+树套树的做法其实是一个板子,将整体二分的第k小板子的树状数组改成树状数组套线段树即可…
复杂度: O ( n log ⁡ 3 n ) O(n\log^3 n) O(nlog3n)

[实现]

暴力

#include
#include
#include
#include
using namespace std;
#define MAXN 60000
#define INF 1000000000
int n,m,val;
int id[MAXN+1];
struct point{
	int x,y,z,id;
}p[MAXN+8];
bool cmp(point s1,point s2){
	return s1.z<s2.z;
}
int read(){
	int x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	p[i].x=read(),p[i].y=read(),p[i].z=read(),p[i].id=i;
	sort(p+1,p+n+1,cmp);
	for(int i=1;i<=n;i++)id[p[i].id]=i;
	for(int i=1;i<=m;i++)
	{
		char s[10];
		scanf("%s",s+1);
		if(s[1]=='Q'){
			int x0=read(),y0=read(),x1=read(),y1=read();
			int k=read();
			if(x0>x1)swap(x0,x1);
			if(y0>y1)swap(y0,y1);
			int nr=0;
			for(int j=1;j<=n;j++)
			if(p[j].x>=x0&&p[j].x<=x1&&p[j].y>=y0&&p[j].y<=y1)
			if(nr<k){nr++,val=p[j].z;}
			if(nr<k){printf("It doesn't exist.\n");continue;}
			printf("%d\n",val);
		}
		else if(s[1]=='S')
		{
			int x=read()+1,y=read()+1;
			swap(p[id[x]].x,p[id[y]].x);
			swap(p[id[x]].y,p[id[y]].y);
			swap(id[x],id[y]);
		}
	}
}

整体二分+树套树

其实这个代码交上去会MLE…但我已经不想再改了…
5KB的代码写起来真爽…

#include
#include
#include
#include
using namespace std;
#define MAXN 60000
#define MAXM 12000000
#define MAXQ 100000
#define lowbit(x) x&(-x)
int n,m,tot;
int x[MAXN+5],y[MAXN+5],p[MAXN+5];
int px[MAXN+5],py[MAXN+5],pp[MAXN+5];
int ans[MAXN+5];
int read(){
	int x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}
struct Qry{
	int kd,x1,y1,x2,y2,pos,val,qid;
}Q[MAXQ+1],Q1[MAXQ+1],Q2[MAXQ+1];
void Discret(int *a,int *tmp){
	for(int i=1;i<=n;i++)tmp[i]=a[i];
	sort(tmp+1,tmp+n+1);
	tmp[0]=unique(tmp+1,tmp+n+1)-tmp-1;
	for(int i=1;i<=n;i++)
	a[i]=lower_bound(tmp+1,tmp+tmp[0]+1,a[i])-tmp;
}
struct Fen_tree{
	int c[MAXN+5],Rx,Ry;
	void Init(int x,int y){Rx=x,Ry=y;memset(c,0,sizeof(c));}
	struct Seg_tree{
		struct node{
			int l,r,sum;
		}p[MAXM+5];
		int Stk[MAXM+1],pcnt;
		Seg_tree(){pcnt=0;for(int i=MAXM;i;i--)Stk[++pcnt]=i;}
		int New(){return Stk[pcnt--];}
		void Del(int x){p[x].l=p[x].r=0;Stk[++pcnt]=x;}
		void Insert(int &x,int l,int r,int pos,int val)
		{
			if(!x)x=New();
			int mid=(l+r)>>1;
			if(l==r){
				p[x].sum+=val;
				if(!p[x].sum)Del(x),x=0;
				return ;
			}
			if(pos<=mid)Insert(p[x].l,l,mid,pos,val);
			else Insert(p[x].r,mid+1,r,pos,val);
			p[x].sum=p[p[x].l].sum+p[p[x].r].sum;
			if(!p[x].sum)Del(x),x=0;
		}
		int Query(int x,int l,int r,int L,int R)
		{
			if(!x||R<l||L>r)return 0;
			int mid=(l+r)>>1;
			if(L<=l&&R>=r)return p[x].sum;
			return Query(p[x].l,l,mid,L,R)+Query(p[x].r,mid+1,r,L,R);
		}
	}T;
	void Insert(int x,int y,int val){
		while(x<=Rx)
		{T.Insert(c[x],1,Ry,y,val);x+=lowbit(x);}
	}
	int Query(int x,int l,int r){
		int res=0;
		while(x>0)
		{res+=T.Query(c[x],1,Ry,l,r);x-=lowbit(x);}
		return res;
	}
}T;
void slove(int l,int r,int L,int R)
{
	if(l>r)return ;
	if(L==R){
		for(int i=l;i<=r;i++)
		if(Q[i].kd)ans[Q[i].qid]=L;
		return ;
	}
	int mid=(L+R)>>1,lc=0,rc=0;
	for(int i=l;i<=r;i++)
	if(Q[i].kd){
		int tmp=T.Query(Q[i].x2,Q[i].y1,Q[i].y2)-T.Query(Q[i].x1-1,Q[i].y1,Q[i].y2);
		if(tmp<Q[i].val)
		Q[i].val-=tmp,Q2[++rc]=Q[i];
		else Q1[++lc]=Q[i];
	}else{
		if(Q[i].pos<=mid)
		T.Insert(Q[i].x1,Q[i].y1,Q[i].val),Q1[++lc]=Q[i];
		else Q2[++rc]=Q[i];
	}
	for(int i=1;i<=lc;i++)
	if(!Q1[i].kd)
	T.Insert(Q1[i].x1,Q1[i].y1,-Q1[i].val);
	for(int i=l;i<=l+lc-1;i++)Q[i]=Q1[i-l+1];
	for(int i=l+lc;i<=r;i++)Q[i]=Q2[i-l-lc+1];
	slove(l,l+lc-1,L,mid);
	slove(l+lc,r,mid+1,R);
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	x[i]=read(),y[i]=read(),p[i]=read();
	Discret(x,px);Discret(y,py);Discret(p,pp);
	for(int i=1;i<=n;i++)
	Q[++tot]=(Qry){0,x[i],y[i],0,0,p[i],1,0};
	int ID=0;
	for(int i=1;i<=m;i++){
		char s[10];
		scanf("%s",s);
		if(s[0]=='S'){
			int a=read()+1,b=read()+1;
			if(a==b)continue;
			Q[++tot]=(Qry){0,x[a],y[a],0,0,p[a],-1,0};
			Q[++tot]=(Qry){0,x[a],y[a],0,0,p[b],1,0};
			Q[++tot]=(Qry){0,x[b],y[b],0,0,p[b],-1,0};
			Q[++tot]=(Qry){0,x[b],y[b],0,0,p[a],1,0};
			swap(p[a],p[b]);
		}else{
			int x1=read(),y1=read(),x2=read(),y2=read(),k=read();
			if(x1>x2)swap(x1,x2);if(y1>y2)swap(y1,y2);
			x1=lower_bound(px+1,px+px[0]+1,x1)-px;
			x2=upper_bound(px+1,px+px[0]+1,x2)-px-1;
			y1=lower_bound(py+1,py+py[0]+1,y1)-py;
			y2=upper_bound(py+1,py+py[0]+1,y2)-py-1;
			if(x1>x2||y1>y2)ans[++ID]=pp[0]+1;
			else Q[++tot]=(Qry){1,x1,y1,x2,y2,0,k,++ID};
		}
	}
	T.Init(px[0],py[0]);
	slove(1,tot,1,pp[0]+1);
	for(int i=1;i<=ID;i++)
	if(ans[i]==pp[0]+1)printf("It doesn't exist.\n");
	else printf("%d\n",pp[ans[i]]);
}

Contra (contra)

[题解]

算法标签:二分矩阵快速幂

首先我们显然二分 p p p,每次算出来和 S S Scheck一下即可.
接着是一个基本的dp。
定义 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]为当前在第 i i i关,现在这轮可以得到 j j j分,现在还有 k k k条生命的概率
注意是概率,因为实际上期望并不好统计…
我们每次统计一次当前步的概率乘上得分即可…
下面是简单实现.

	dp[0][0][q]=1;
	DB ans=0;
	for(int i=0;i<n;i++)
		for(int j=0;j<=min(i,r);j++)
			for(int k=1;k<=q;k++){
			dp[i+1][min(j+1,r)][min(k+1,q)]+=(dp[i][j][k]*p);
			dp[i+1][0][k-1]+=(dp[i][j][k]*(1-p));
			ans=ans+dp[i][j][k]*p*min(j+1,r);
			}

现在,我们要用矩阵乘法做相同的工作。
具体来说,我们可以开一个 r ∗ q r*q rq的矩阵,再加一个节点表示结束节点.
将上面的dp过程模拟一遍即可。
复杂度: O ( R 3 Q 3 log ⁡ n ) O(R^3Q^3 \log n) O(R3Q3logn)

[实现]

#include
#include
#include
#include
using namespace std;
#define MAXN 40
#define DB double
int n,r,q;
DB s;
int num[MAXN+1][MAXN+1];
struct Matrix{
	DB p[MAXN+1][MAXN+1];
	Matrix(){memset(p,0,sizeof(p));}
	Matrix operator*(const Matrix& y)const{
		Matrix res;
		for(int i=0;i<MAXN;i++)
            for(int j=0;j<MAXN;j++)
				for(int k=0;k<MAXN;k++)
				res.p[i][k]=res.p[i][k]+p[i][j]*y.p[j][k];
		return res;
	}
};
Matrix fst_pow(Matrix A,int b)
{
	Matrix res;
	for(int i=0;i<MAXN;i++)
		res.p[i][i]=1;
	while(b){
		if(b&1)res=res*A;
		A=A*A;
		b>>=1;
	}
	return res;
}
bool check(DB p)
{
	Matrix A,B;int id=0;
	for(int i=0;i<=q;i++){
		int k=min(i-1,r);
		if(i==q)k=r; 
		//方便统计答案 
		for(int j=0;j<=max(k,0);j++)
		num[i][j]=++id;
	}
	A.p[1][1]=1;//num[0][0](=1)为end点
	for(int i=1;i<=q;i++){
		int k=min(i-1,r);
		if(i==q)k=r; 
		for(int j=0;j<=max(k,0);j++){
			int x=num[i][j],y=num[i-1][0];
			int z=num[min(i+1,q)][min(j+1,r)];
			A.p[x][y]=1-p,A.p[x][z]=p,A.p[x][1]=p*min(j+1,r);
		}
	}
	Matrix ans=fst_pow(A,n);
	return (ans.p[num[q][0]][1]>s);
}
int main()
{
	scanf("%d%d%d%lf",&n,&r,&q,&s);
	int l=0,r=10000000,p=r;
	if(!check(1))
	{printf("Impossible.");return 0;}
	while(l+1<r)
	{
		int mid=(l+r)/2;
		if(check((double)mid/p))r=mid;
		else l=mid;
	}
	printf("%.6f",(double)l/p);
}

Bomb (bomb)

[题解]

算法标签:线段树/分治

首先答案显然就是 2 ∗ ( X m a x − X m i n + Y m a x − Y m i n ) 2*(X_{max}-X_{min}+Y_{max}-Y_{min}) 2(XmaxXmin+YmaxYmin)

显然 X , Y X,Y X,Y独立。

先考虑最大值,由于只有3个点,那么显然我们找到, X X X Y Y Y, X + Y X+Y X+Y, X − Y X-Y XY四个值得最小和最大值,然后组合一下即可。

找最小值就比较麻烦了。

正解的思路是线段树,考虑我们还是利用上述式子。

考虑答案点 a a a, b b b, c c c,满足 X a < X b < X c X_aXa<Xb<Xc,我们考虑枚举 b b b点(其实枚举那个都一样)

然后开始讨论:

  1. b b b点没有贡献,我们维护其他点的 x + y x+y x+y, x − y x-y xy的最小与最大即可。
  2. 如果 b b b点有贡献,那它只能够作为 y y y的最大值和最小值,然后再往左右两边讨论选哪个点即可,用线段树维护即可。

然后还有一种分治乱搞算法…

先将点按 x , y x,y x,y双关键字排序,考虑只在 [ l , r ] [l,r] [l,r]区间内计算最小值。

当区间大小小于 12 12 12时,为了保证复杂度,我们直接暴力。

否则每次我们选择一个点 p o s pos pos进行划分,先计算 [ l , p o s ] , [ p o s + 1 , r ] [l,pos],[pos+1,r] [l,pos],[pos+1,r],然后仅计算 X m i n − X m a x X_{min}-X_{max} XminXmax经过 X p o s X_{pos} Xpos点的最小三角形周长,然后有一个简单的优化,就是当 ∣ X i − X p o s ∣ |X_i-X_{pos}| XiXpos已经大于当前最小答案,显然这是不优的。

然后对符合条件的点按 x , y x,y x,y双关键字排序,枚举 y y y最大的点,筛去与当前 y y y差距大于当前最小答案的点,然后暴力枚举符合条件的点更新即可。

如果我们随机化选择的 p o s pos pos,这样会更快一些,当然,不随机化也是可以过的…

[实现]

只写了分治乱搞法…

因为T1 4KB+就要自闭了,更别说这个6KB+的代码了…

#pragma GCC optimize(2)
#include
#include
#include
using namespace std;
#define MAXN 100000
#define INF 1000000000
int n;
int ansmin,ansmax,xmin,xmax,ymin,ymax,k1min,k1max,k2min,k2max;
struct point{
	int x,y;
}a[MAXN+5],tmp[MAXN+5];
int dis(point i,point j,point k) {
	return 2*max(i.x,max(j.x,k.x))-2*min(i.x,min(j.x,k.x))+2*max(i.y,max(j.y,k.y))-2*min(i.y,min(j.y,k.y));
}
bool cmp1(point s1,point s2){
	if(s1.x==s2.x)return s1.y<s2.y;
	return s1.x<s2.x;
}
bool cmp2(point s1,point s2){
	if(s1.y==s2.y)return s1.x<s2.x;
	return s1.y<s2.y;
}
void divis(int l,int r){
	int siz=r-l+1;
	if(siz<12){
		for(int i=l;i<=r;i++)
			for(int j=i+1;j<=r;j++)
				for(int k=j+1;k<=r;k++)
				ansmin=min(ansmin,dis(a[i],a[j],a[k]));
		return ;
	}
	int mid=(l+r)>>1;
	divis(l,mid),divis(mid+1,r);
	int tp=0;
	for(int i=mid+1;i<=r;i++)
	if(a[i].x-a[mid].x<ansmin)tmp[++tp]=a[i];
	else break;
	for(int i=mid-1;i>=l;i--)
	if(a[mid].x-a[i].x<ansmin)tmp[++tp]=a[i];
	else break;
	sort(tmp+1,tmp+tp+1,cmp2);
	int L=1;
	for(int R=3;R<=tp;R++){
		while(tmp[R].y-tmp[L].y>=ansmin)L++;
		for(int j=L;j<R;j++)
			for(int k=j+1;k<R;k++)
			ansmin=min(ansmin,dis(tmp[j],tmp[k],tmp[R]));
	}
}
int main()
{
	scanf("%d",&n);
	ansmax=-INF,ansmin=INF;
	xmin=INF,xmax=-INF,ymin=INF,ymax=-INF,k1min=INF,k1max=-INF,k2min=INF,k2max=-INF;
	for(int i=1;i<=n;i++){
		scanf("%d%d",&a[i].x,&a[i].y);
		xmin=min(xmin,a[i].x),xmax=max(xmax,a[i].x),ymin=min(ymin,a[i].y),ymax=max(ymax,a[i].y);
		k1min=min(k1min,a[i].x+a[i].y);k1max=max(k1max,a[i].x+a[i].y);
		k2min=min(k2min,a[i].x-a[i].y);k2max=max(k2max,a[i].x-a[i].y);
	}
	ansmax=max(k1max-xmin-ymin,max(xmax+ymax-k1min,max(k2max+ymax-xmin,xmax-ymin-k2min)))*2;
	sort(a+1,a+n+1,cmp1);
	divis(1,n);
	printf("%d\n%d\n",ansmax,ansmin);
}

你可能感兴趣的:(游记合辑)