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
我们将元素应该在的位置和它当前位置连边,可以得到这样的图:
一个显然的贪心思路是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
这样可以把这个环拆成若干个小环,比如这样:
进一步观察后可以发现,每个环都可以用这种方法拆成若干个长度为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