【题目记录】——第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(昆明)


第 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] biwiϵ[li,ri]
对任一列j, B j − W j ϵ [ L i , R i ] B_j-W_j\epsilon[L_i,R_i] BjWjϵ[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][j1],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");
     }
 }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值