ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

2021ICPC亚洲区域赛(昆明)复盘

2021-04-04 16:35:20  阅读:268  来源: 互联网

标签:return int double 2021ICPC maxn ans x1 复盘 昆明


2021ICPC亚洲区域赛(昆明)复盘

Wogua_boy

I.Mr. Main and Windmills(计算几何)

题意:

Mr.Main坐火车从s到t,经过了许多风车。

火车在一条直线上行驶。

随着火车的行驶,风车在Mr.Main的视野里会发生位置相对变化。

现在给出风车们的坐标,请你找到当第h个风车与其他风车的相对位置变化k次时火车所在的坐标。

题解:

观察后发现,只需要取风车坐标两两之间直线和s到t线段的交点然后排序就好了。

比赛的时候因为天生对计算几何的恐惧直接扔给队友了。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1010;
const double eps=1e-8;
int n,m;
double xs,ys,xt,yt; 
int sgn (double x) {
	if (fabs(x)<eps) return 0;
	else if (x<0) return -1;
	else return 1;
}
double x[maxn],y[maxn];
vector<pair<double,double> > p[maxn];
//每个点和剩下所有点连成的直线与母线的交点
pair<double,double> jd (double x1,double y1,double x2,double y2,double x3,double y3,double x4,double y4) {
	if (x1==x2&&y1==y2) return make_pair(1e18,1e18);
	//(x1,y1),(x2,y2)组成的直线和(x3,y3),(x4,y4)组成的直线的交点
	double k1=(x1==x2?1e18:(y1-y2)/(x1-x2));
	double k2=(y1==y2?1e18:(y3-y4)/(x3-x4));
	if (sgn(k1-k2)==0) return make_pair(1e18,1e18);
	//k1是1e18,k2不是,答案就是x1*k2+b2
	if (k1==1e18) {
		return make_pair(x1,x1*k2+y3-k2*x3);
	} 
	else if (k2==1e18) {
		return make_pair(x2,x2*k1+y1-k1*x1);
	}
	double b1=y1-k1*x1;
	double b2=y3-k2*x3;
	double x=(b2-b1)/(k1-k2);
	double y=k1*x+b1;
	return make_pair(x,y);
}
int cmp (pair<double,double> x,pair<double,double> y) {
	if (sgn(x.first-y.first)!=0) {
		return sgn(x.first-y.first)<0;
	}
	else {
		return sgn(x.second-y.second)<0;
	}
} 
int main () {
	scanf("%d%d",&n,&m);
 	scanf("%lf%lf%lf%lf",&xs,&ys,&xt,&yt);
 	for (int i=1;i<=n;i++) scanf("%lf%lf",x+i,y+i);
 	for (int i=1;i<=n;i++) {
 		for (int j=1;j<=n;j++) {
 			if (i==j) continue;
 			pair<double,double> it=jd(x[i],y[i],x[j],y[j],xs,ys,xt,yt);	
 			if (it.first==1e18) continue;//没有交点
			if (sgn(it.first-min(xs,xt))<0||sgn(it.first-max(xs,xt))>0||sgn(it.second-min(ys,yt))<0||sgn(it.second-max(ys,yt))>0) continue;//交点在线段以外
			p[i].push_back(it); 
		}
		if (xs<xt) sort(p[i].begin(),p[i].end(),cmp);
		else if (xs>xt) sort(p[i].rbegin(),p[i].rend(),cmp);
		else if (ys<yt) sort(p[i].begin(),p[i].end(),cmp);
		else sort(p[i].rbegin(),p[i].rend(),cmp);
	}
	while (m--) {
		int h,t;
		scanf("%d%d",&h,&t);
		//printf("%d\n",p[h].size());
		if (t>p[h].size()) {
			printf("-1\n");
			continue;
		}
		printf("%.10f %.10f\n",p[h][t-1].first,p[h][t-1].second);
	}
}

J.Parallel Sort(构造)

题意:

给出一个数组,单轮可以交换任意两个元素的值,但是同一个下标在一轮中只能调用一次。

询问至少几轮可以使数组有序?

题解:

比赛的时候在错误的贪心思路陷进去了。觉得\(10^5\)的数据范围,\(nlogn\)复杂度很对。但实际上只需要两次即可。

考虑这样一组样例:

6
2 3 4 5 6 1

我们将元素应该在的位置和它当前位置连边,可以得到这样的图:

image-20210404145636097

一个显然的贪心思路是2和3连边,4和5连边,6和1连边,然后第二轮继续这个贪心方法,即遇到能换的就换,扩展之后可以发现大概需要\(logn\)次可以完成排序的任务。

但是有一个更加优秀的构造方法。观察之后可以发现,任意排列通过图表示,都是一个一个独立的环。

对于这个环,第一轮分别交换[1,2],[3,6],[4,5],这样可以得到一个这样的数组:

6
2 3 4 5 6 1
1 6 5 4 3 2

这样可以把这个环拆成若干个小环,比如这样:

image-20210404150146840

进一步观察后可以发现,每个环都可以用这种方法拆成若干个长度为2的小环。

第二轮对每个环交换一次即可。

所以稳定小于等于2次。

做法就是先处理出每个环的信息,然后将每个环的第一个元素和倒数第一个元素、第二个元素和倒数第二个元素...交换,可以把环拆成若干个大小小于等于2的小环。

第二轮就一步到位了。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+100;
int n,p[maxn];
int vis[maxn];
vector<pair<int,int> > ans[2];
int b[maxn];
int main () {
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",p+i),b[p[i]]=i;
	for (int i=1;i<=n;i++) {
		if (p[i]==i) continue;
		if (vis[p[i]]) continue;
		int u=p[i];
		vector<int> tt;
		while (p[u]!=p[i]) {
			tt.push_back(b[u]);
			u=p[u];
		}
		for (int j=0;j<tt.size()/2;j++) {
			swap(p[tt[j]],p[tt[tt.size()-j-1]]);
			b[p[tt[j]]]=tt[j];
			b[p[tt[tt.size()-j-1]]]=tt[tt.size()-j-1];
			ans[0].push_back(make_pair(tt[j],tt[tt.size()-j-1]));
		}
	}
	int f=1;
	for (int i=1;i<=n;i++) if (p[i]!=i) f=0;
	if (f) {
		if (ans[0].size()==0) return printf("0"),0;
		printf("1\n");
		printf("%d",ans[0].size());
		for (pair<int,int> i:ans[0]) printf(" %d %d",i.first,i.second);
		printf("\n");
		return 0;
	}
	for (int i=1;i<=n;i++) {
		if (p[i]==i) continue;
		ans[1].push_back(make_pair(i,b[i]));
		swap(p[i],p[b[i]]);
		b[p[i]]=i;
		b[p[b[i]]]=b[i];
	}
	int cnt=((ans[0].size()>0?1:0)+(ans[1].size()>0?1:0)); 
	printf("%d\n",cnt);
	for (int i=0;i<2;i++) {
		if (!ans[i].size()) continue; 
		printf("%d",ans[i].size());
		for (pair<int,int> j:ans[i]) printf(" %d %d",j.first,j.second);
		printf("\n");
	}
}

M.Stone Games(结论推导+可持久化线段树维护)

题意:

给出一个数组,每次询问一个区间,请你找到最小的x,使得这个区间里的数通过加法凑不出x。

强制在线。

题解:

这道题应该是2019ICPC徐州的弱化版本。

首先,没有1的话答案就是1。

然后2的话,求小于2的所有数的和,设这个和为sum,如果sum<2,那么答案就是2,否则[1~sum]都可以被表示出来。

再去找比sum+1小的数的和,如果这个和小于sum+1,说明答案就是sum+1,否则找到sum+1继续相同的操作。

那么做法就是:先对所有的数离散化,建可持久化线段树。

然后,用可持久化线段树维护区间比某个数小的数的和即可。这个和是每次近似两倍的速度增长的,时间复杂度大概是\(O(32nlogn)\)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+100;
const int M=maxn*40;
int a[maxn],t[maxn];
int T[maxn];
int lson[M];
int rson[M];
ll c[M];
int tot;
int n,m,q;
int build (int l,int r) {
	int root=tot++;
	c[root]=0;
	if (l!=r) {
		int mid=(l+r)>>1;
		lson[root]=build(l,mid);
		rson[root]=build(mid+1,r);
	}
	return root;
}
int up (int root,int p,int v) {
	int newRoot=tot++;
	int tmp=newRoot;
	int l=1,r=m;
	c[newRoot]=c[root]+t[p];
	while (l<r) {
		int mid=(l+r)>>1;
		if (p<=mid) {
			lson[newRoot]=tot++;
			rson[newRoot]=rson[root];
			newRoot=lson[newRoot];
			root=lson[root];
			r=mid;
		}
		else {
			rson[newRoot]=tot++;
			lson[newRoot]=lson[root];
			newRoot=rson[newRoot];
			root=rson[root];
			l=mid+1;
		}
		c[newRoot]=c[root]+t[p];
	}
	return tmp;
}
ll query (int left_root,int right_root,int l,int r,int L,int R) {
	if (L>R) return 0;
	if (l>=L&&r<=R) return c[left_root]-c[right_root];
	int mid=(l+r)>>1;
	ll ans=0;
	if (L<=mid) ans+=query(lson[left_root],lson[right_root],l,mid,L,R);
	if (R>mid) ans+=query(rson[left_root],rson[right_root],mid+1,r,L,R);
	return ans;
}
int main () {
	scanf("%d%d",&n,&q);
	for (int i=1;i<=n;i++) scanf("%d",a+i),t[i]=a[i];
	sort(t+1,t+n+1);
	m=unique(t+1,t+n+1)-t-1;
	for (int i=1;i<=n;i++) a[i]=upper_bound(t+1,t+m+1,a[i])-t-1;
	ll ans=0;
	T[n+1]=build(1,m);
	for (int i=n;i>=1;i--) T[i]=up(T[i+1],a[i],1);
	while (q--) {
		ll l,r;
		scanf("%lld%lld",&l,&r);
		ll ql=min((l+ans)%n+1,(r+ans)%n+1);
		ll qr=max((l+ans)%n+1,(r+ans)%n+1);
		//printf("%lld %lld\n",ql,qr);
		ll tt=1;
		while (1) {
			ll t1=-1;
			int L=1,R=m;
			while (L<=R) {
				int mid=(L+R)>>1;
				if (t[mid]<=tt) {
					t1=mid;
					L=mid+1;
				}
				else{
					R=mid-1;
				}
			}
			ll t2=query(T[ql],T[qr+1],1,m,1,t1);
			if (t2<tt) {
				ans=tt;
				break;
			}
			tt=t2+1;
		}
		printf("%lld\n",ans);
	}
}

标签:return,int,double,2021ICPC,maxn,ans,x1,复盘,昆明
来源: https://www.cnblogs.com/zhanglichen/p/14616364.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有