文章目录
第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(昆明)
我们只做了H题,水题。。
出题人评价及部分题解 如何评价2021年 ICPC 昆明赛区?
部分题解 ICPC昆明游记(2021.4.3)
B Chessboard 上下界最小费用最大流
题目地址B Chessboard
题目大意:一个n*m的棋盘,你可以放黑色棋子,也可以放白色棋子在上面,在(i,j)位置放黑色棋子你会得到
s
b
i
j
sb_{ij}
sbij分数,放置白色棋子你会得到
s
w
i
j
sw_{ij}
swij分数。一个格子只能放置一个棋子。我们用
b
i
b_i
bi表示第i行黑色棋子的数量,
B
i
B_i
Bi表示第i列黑色棋子的数量,
w
i
w_i
wi表示第i行白色棋子的数量,
W
i
W_i
Wi表示第i列白色棋子的数量。给出条件
对任一行i,
b
i
−
w
i
ϵ
[
l
i
,
r
i
]
b_i-w_i\epsilon[l_i,r_i]
bi−wiϵ[li,ri]
对任一列j,
B
j
−
W
j
ϵ
[
L
i
,
R
i
]
B_j-W_j\epsilon[L_i,R_i]
Bj−Wjϵ[Li,Ri]
求满足条件所能得到的最小分数。
网络流方面的内容还不是我们这种蒟蒻能解决的先放一放
参考博客2021ICPC 昆明区域赛 B.Chessboard(上下界最小费用最大流)
C cities 区间dp
题目地址C cities
题目大意:给定一个长度n的数组
a
i
∈
[
1
,
n
]
a_i\in[1,n]
ai∈[1,n]且没有元素出现过多于15次,
现在每次可以把一段数字相同的区间变成另一种数字,问,最少操作多少次使得a数组只剩下一种数字。
思路:参考文章2021 icpc昆明 C.Cities(区间dp)
比较明显的一个区间dp的题目,但是直接进行dp是不行的,需要考虑如何减少枚举次数。
定义
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示
[
i
,
j
]
[i,j]
[i,j]染成一种颜色的最小操作次数
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
]
[
j
−
1
]
,
f
[
i
+
1
]
[
j
]
)
+
1
f[i][j]=min(f[i][j-1],f[i+1][j])+1
f[i][j]=min(f[i][j−1],f[i+1][j])+1
但是当两端相等时我们可以节省一次操作
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
]
[
k
]
+
f
[
k
+
1
]
[
j
]
)
f[i][j]=min(f[i][k]+f[k+1][j])
f[i][j]=min(f[i][k]+f[k+1][j])
此时需要满足
a
[
k
]
=
=
a
[
j
]
a[k]==a[j]
a[k]==a[j],把左边染成k颜色,右边染成j颜色
题目保证了k不超过15次
所以初始先把颜色相同的缩点,然后去转移
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5009;
int t,n,a[maxn],col[maxn],f[maxn][maxn],top,nxt;
int pre[maxn],las[maxn];
int main()
{
cin >> t;
while( t-- )
{
cin >> n; top = 0;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=2;i<=n;i++)
{
if( a[i]==a[i-1] ) continue;
else col[++top] = a[i-1];
}
memset( pre,0,sizeof pre );
memset( las,0,sizeof las );
col[++top] = a[n];
for(int i=1;i<=top;i++)
pre[i] = las[col[i]],las[col[i]] = i;
memset( f,20,sizeof f );
for(int i=1;i<=top;i++) f[i][i] = 0;
for(int l=2;l<=top;l++)
for(int i=1;i+l-1<=top;i++)
{
int j = i+l-1;
f[i][j] = min( f[i+1][j],f[i][j-1] )+1;
for(int k=pre[j];k>=i;k=pre[k])
f[i][j] = min( f[i][j],f[i][k]+f[k+1][j]);
}
cout << f[1][top] << endl;
}
}
H Hard Calculation 简单题
组长说不用整理,我说不行,这么难的题目怎么能不整呢?
题目地址H Hard Calculation
题目大意:2021年举办的第一届ICPC,一年举行一届,问第n届在那一年举行。
思路:。。。。
AC代码:
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
int main()
{
int n;
cin >> n;
cout << 2020+n << endl;
return 0;
}
I Mr. Main and Windmills 计算几何
题目地址I Mr. Main and Windmills
题目大意:Mr.Main坐火车从s到t,经过了许多风车。
火车在一条直线上行驶。
随着火车的行驶,风车在Mr.Main的视野里会发生位置相对变化。
现在给出风车们的坐标,请你找到当第h个风车与其他风车的相对位置变化k次时火车所在的坐标。
思路:只需要取风车坐标两两之间直线和s到t线段的交点然后排序就好了。但是我们当时不会求直线和线段的交点。。
AC代码:
#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 思维+模拟
题目地址J Parallel Sort
题目大意:给出一个数组,单轮可以交换任意两个元素的值,但是同一个下标在一轮中只能调用一次。
询问至少几轮可以使数组有序?
思路:将一个数和他要交换到的位置的数连接起来,观察之后可以发现,任意排列通过图表示,都是一个一个独立的环。
对于这个环,第一轮分别交换[1,2],[3,6],[4,5],这样可以得到一个这样的数组:
6
2 3 4 5 6 1
1 6 5 4 3 2
进一步观察后可以发现,每个环都可以用这种方法拆成若干个长度为2的小环。
第二轮对每个环交换一次即可。
所以稳定小于等于2次。
做法就是先处理出每个环的信息,然后将每个环的第一个元素和倒数第一个元素、第二个元素和倒数第二个元素交换,可以把环拆成若干个大小小于等于2的小环。
第二轮就一步到位了。
具体的解释参考ICPC昆明游记(2021.4.3)
发现规律后是比较简单的,但是这个规律还真的是不好找。
AC代码:
#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");
}
}