其他分享
首页 > 其他分享> > 2021ICPC亚洲区域赛(昆明)复盘

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

作者:互联网

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