YMOI 2019.6.22

题解 YMOI 2019.6.22

lia麦頔溜了,缺了lia麦頔的排名仅供参考

不过分数还是暴露无遗

T1 邪恶入侵

简易题干:

在三维空间内有一些点,点之间有双向边。每一次询问给出一个m,只有边权小于等于m的边才可以同行。每一次询问需要回答出,从源点可联通的最大点数。

毕竟摆在了T1的位置,再加上最近相关练习不少。考场上还是很顺利的想出了正解

由于答案不强制在线,那么先将询问按m升序排序,然后用并查集维护点之间的联系即可

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;

const int MAX=1e3+5;

int n,m,src,ecnt;
int fa[MAX],fa_size[MAX];
int pos;

struct data{
	ll x,y,z;
}node[MAX];

struct datae{
	int u,v; double dis;
}edge[MAX*MAX];

struct  dataa{
	ll m;int id,ans;
}query[500005];

inline int read();
inline ll readll();
double dis(int,int);
bool cmpd(datae,datae);
bool cmpm(dataa,dataa);
bool cmpi(dataa,dataa);

void init();
int find(int);
void unionn(int,int);
int find_size(int);

int main(){
	#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
	#else
	freopen("invade.in","r",stdin);
	freopen("invade.out","w",stdout);
	#endif

	n=read(); m=read(); src=n+1;
	for(int i=1;i<=n;++i) node[i].x=readll(),node[i].y=readll(),node[i].z=readll();

	for(int i=1;i<=src;++i){
		for(int j=i+1;j<=src;++j){
			ecnt++;
			edge[ecnt].u=i; edge[ecnt].v=j; edge[ecnt].dis=dis(i,j);
		}
	}
	sort(edge+1,edge+1+ecnt,cmpd);

	for(int i=1;i<=m;++i) query[i].m=read(),query[i].id=i;
	sort(query+1,query+1+m,cmpm);

	init();
	for(int k=1;k<=m;++k){
		while(pos<ecnt&&edge[pos+1].dis<query[k].m){
			pos++;
			int u=edge[pos].u,v=edge[pos].v;
			if(find(u)!=find(v)) unionn(u,v);
		}
		query[k].ans=find_size(src);
	}
	sort(query+1,query+1+m,cmpi);

	for(int i=1;i<=m;++i) printf("%d\n",query[i].ans-1);

	return 0;
}

inline int read(){
	char tmp=getchar(); int sum=0; bool flag=false;
	while(tmp<'0'||tmp>'9'){
		if(tmp=='-') flag=true;
		tmp=getchar();
	}
	while(tmp>='0'&&tmp<='9'){
		sum=(sum<<1)+(sum<<3)+tmp-'0';
		tmp=getchar();
	}
	return flag?-sum:sum;
}

inline ll readll(){
	char tmp=getchar(); ll sum=0; bool flag=false;
	while(tmp<'0'||tmp>'9'){
		if(tmp=='-') flag=true;
		tmp=getchar();
	}
	while(tmp>='0'&&tmp<='9'){
		sum=(sum<<1)+(sum<<3)+tmp-'0';
		tmp=getchar();
	}
	return flag?-sum:sum;
}

double dis(int a,int b){
	return sqrt((node[a].x-node[b].x)*(node[a].x-node[b].x)+(node[a].y-node[b].y)*(node[a].y-node[b].y)+(node[a].z-node[b].z)*(node[a].z-node[b].z));
}

bool cmpd(datae a,datae b){
	return a.dis<b.dis;
}

bool cmpm(dataa a,dataa b){
	return a.m<b.m;
}

bool cmpi(dataa a,dataa b){
	return a.id<b.id;
}

void init(){
	for(int i=1;i<=src;++i) fa[i]=i,fa_size[i]=1;
}

int find(int a){
	if(fa[a]!=a) return find(fa[a]);
	return fa[a];
}

void unionn(int a,int b){
	int fa_a=find(a),fa_b=find(b);
	fa[fa_a]=fa_b;
	fa_size[fa_b]+=fa_size[fa_a];
}

int find_size(int a){
	return fa_size[find(a)];
}

T2 魔法照片

题目传送门

这道题在考场自以为想出了正解,结果发现只是个暴力,而且!一失误一分不得!>﹏<

考场的思路是酱紫的:

维护一个平衡树,这样求中位数是一个\(O(\log n)\)。通过S形转移,可以保证每一个节点,最多只会被插入一次,弹出一次。因此总理论复杂度是\(O(n \log n)\),看起来是正解。

但是!考场上我把lson打成了rson,还稀里糊涂地过了样例。。愉悦白给。后来事实证明平衡树由于常数原因,只能过60%的测试点虽然这对我来说已经很满足了

说正解:

题解里说

“中位数最大”使我们想到了二分答案。

那就假装想到了吧。二分需要答案满足单调性,那就特殊地构造一下。将要求的“中位数”,修改为“小于等于中位数”

考虑中位数 x

如果有奇数个数字,则大于x的数字与小于x的数字个数相同

如果有偶数个数字,则大于x的数字与小于x的数字个数相差1

等于x的数字则随意放置。

假设所有大于等于x的数字为1,小于x的数字为-1,则当所有数字的个数大于等于0,则x是小于等于中位数

那么二分答案之后用二维前缀和求一下。复杂度\(O(n\times m \log a)\)

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int MAX=1005;

int n,m,s,l;
int ll,rr=1000000000;

inline int read();

int main(){
	#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
	#else
	freopen("photo.in","r",stdin);
	freopen("photo.out","w",stdout);
	#endif

	n=read(); m=read(); s=read(); l=read();

	int w[n+5][m+5],sum[n+5][m+5];
	for(int i=0;i<=n;++i) sum[i][0]=0;
	for(int i=0;i<=m;++i) sum[0][i]=0;
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) w[i][j]=read();

	while(ll<=rr){
		int mid=(ll+rr)>>1;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+(w[i][j]>=mid?1:-1);
			}
		}
		bool flag=false;
		for(int i=1;i<=n-s+1;++i){
			for(int j=1;j<=m-l+1;++j){
				if(sum[i+s-1][j+l-1]+sum[i-1][j-1]-sum[i+s-1][j-1]-sum[i-1][j+l-1]>=0){
					flag=true;
					break;
				}
			}
			if(flag) break;
		}
		if(flag) ll=mid+1;
		else rr=mid-1;
	}
	printf("%d\n",rr);

	return 0;
}

inline int read(){
	char tmp=getchar(); int sum=0; bool flag=false;
	while(tmp<'0'||tmp>'9'){
		if(tmp=='-') flag=true;
		tmp=getchar();
	}
	while(tmp>='0'&&tmp<='9'){
		sum=(sum<<1)+(sum<<3)+tmp-'0';
		tmp=getchar();
	}
	return flag?-sum:sum;
}

T3 生命曲线

题目传送门

没有丝毫难度的线段树。 ——某F姓嘟锍出题人

事实上相当明显的数据结构题,如果熟练掌握的话难度确实不大

等差数列的处理:

为线段树的每一个节点记录一个首相和斜率,那么其余值都可以快速推算出来。

需要知道的是:两个等差数列之和还是个等差数列

一个更新的时候的小技巧:当确定当前区间与目标区间的时候,首相与斜率便已经确定下来了。因此无需其它复杂的维护,像区间修改那样单纯的修改即可。本人就是用来一大堆自以为高明的修改方式,结果debug到疯

区间取反的处理:

其实类似于线段树的区间乘法。这里,取法相当于特殊地\(\times -1\)

加法无法作用于乘法,但乘法可以作用于加法,因此可以理解为乘法的优先级高于加法。这样更新的时候,加法的lazy_tag随乘法的更新而更新,而乘法的lazy_tag直接作用于区间记录的val值

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;

const int MAX=5e5+5;

int n,m;
ll init[MAX];
ll val[MAX<<4],lazy_fir[MAX<<4],lazy_del[MAX<<4]; int lazy_rev[MAX<<4];

inline int read();
inline ll readll();
void build(int,int,int);
ll qsum(int,int,int,int,int);
void modify_add(int,int,int,int,int,ll,ll);
void modify_rev(int,int,int,int,int);
void pushdown(int,int,int);
void add(int,int,int,ll,ll,int);

int main(){
	#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	#else
	freopen("life.in","r",stdin);
	freopen("life.out","w",stdout);
	#endif

	n=read(); m=read();
	for(int i=1;i<=n;++i) init[i]=readll();

	build(1,1,n);

	for(int i=1;i<=m;++i){
		int type,l,r; ll a,d; type=read();
		switch(type){
			case 1:
			l=read(); r=read(); a=readll(); d=readll();
			modify_add(1,1,n,l,r,a,d);
			break;
			case 2:
			l=read(); r=read();
			printf("%lld\n",qsum(1,1,n,l,r));
			break;
			case 3:
			l=read(); r=read();
			modify_rev(1,1,n,l,r);
			break;
		}
	}

	return 0;
}

inline int read(){
	char tmp=getchar(); int sum=0; bool flag=false;
	while(tmp<'0'||tmp>'9'){
		if(tmp=='-') flag=true;
		tmp=getchar();
	}
	while(tmp>='0'&&tmp<='9'){
		sum=(sum<<1)+(sum<<3)+tmp-'0';
		tmp=getchar();
	}
	return flag?-sum:sum;
}

inline ll readll(){
	char tmp=getchar(); ll sum=0; bool flag=false;
	while(tmp<'0'||tmp>'9'){
		if(tmp=='-') flag=true;
		tmp=getchar();
	}
	while(tmp>='0'&&tmp<='9'){
		sum=(sum<<1)+(sum<<3)+tmp-'0';
		tmp=getchar();
	}
	return flag?-sum:sum;
}

void build(int p,int l,int r){
	if(l==r) {val[p]=init[l]; return;}
	int mid=(l+r)>>1;
	build(p<<1,l,mid); build(p<<1|1,mid+1,r);
	val[p]=val[p<<1]+val[p<<1|1];
}

ll qsum(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R) return val[p];
	pushdown(p,l,r);
	int mid=(l+r)>>1; ll ans=0;
	if(L<=mid) ans+=qsum(p<<1,l,mid,L,R);
	if(mid+1<=R) ans+=qsum(p<<1|1,mid+1,r,L,R);
	return ans;
}

void modify_add(int p,int l,int r,int L,int R,ll fir,ll del){
	if(L<=l&&r<=R) return add(p,l,r,fir+del*(l-L),del,0);
	pushdown(p,l,r);
	int mid=(l+r)>>1;
	if(L<=mid) modify_add(p<<1,l,mid,L,R,fir,del);
	if(mid+1<=R) modify_add(p<<1|1,mid+1,r,L,R,fir,del);
	val[p]=val[p<<1]+val[p<<1|1];
}

void modify_rev(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R) return add(p,l,r,0,0,1);
	pushdown(p,l,r);
	int mid=(l+r)>>1;
	if(L<=mid) modify_rev(p<<1,l,mid,L,R);
	if(mid+1<=R) modify_rev(p<<1|1,mid+1,r,L,R);
	val[p]=val[p<<1]+val[p<<1|1];
}

void pushdown(int p,int l,int r){
	if(!lazy_fir[p]&&!lazy_del[p]&&!lazy_rev[p]) return;
	int mid=(l+r)>>1;
	add(p<<1,l,mid,lazy_fir[p],lazy_del[p],lazy_rev[p]);
	add(p<<1|1,mid+1,r,lazy_fir[p]+lazy_del[p]*(mid+1-l),lazy_del[p],lazy_rev[p]);
	lazy_fir[p]=0; lazy_del[p]=0; lazy_rev[p]=0;
}

void add(int p,int l,int r,ll fir,ll del,int rev){
	if(rev) {val[p]=-val[p]; lazy_fir[p]=-lazy_fir[p]; lazy_del[p]=-lazy_del[p];}
	val[p]+=(ll)(r-l+1)*fir;
	val[p]+=(ll)(r-l)*(r-l+1)/2*del;
	lazy_fir[p]+=fir; lazy_del[p]+=del;
	lazy_rev[p]=lazy_rev[p]^rev;
}

后记

事实上,本次考试的理想分数应当是:100+60+30=190. 而如果真能达到这个分数,那就是一个相当相当高的分数了。

你太了!

posted @ 2020-06-29 22:56  ticmis  阅读(162)  评论(0编辑  收藏  举报