【题解】Codeforces GYM 2020 ICPC Asia Seoul Regional
【B. Commemorative Dice】
暴力枚举36种情况,对得到的答案约分
1 #include<cstdio> 2 using namespace std; 3 4 int n,a[10],b[10],ans,x; 5 6 inline int gcd(int x,int y) {return x%y?gcd(y,x%y):y;} 7 int main() 8 { 9 n=6; 10 for (int i=1; i<=n; ++i) scanf("%d",&a[i]); 11 for (int i=1; i<=n; ++i) scanf("%d",&b[i]); 12 for (int i=1; i<=n; ++i) for (int j=1; j<=n; ++j) if (a[i]>b[j]) ans++; 13 x=gcd(ans,36); 14 while (1) 15 { 16 if (ans==36) {printf("1"); break;} 17 if (ans==0) {printf("0"); break;} 18 if (x==1) {printf("%d/36",ans); break;} 19 printf("%d/%d",ans/x,36/x); 20 break; 21 } 22 return 0; 23 }
【E. Imprecise Computer】
题目问给出的数列能不能成为一个“差别序列”D
即问我们能不能构造出一种电脑给出的双循环赛的结果,使得其计算出的“差别序列”D恰好是给定数列
那么我们就照着给出的数列来进行构造
首先,对于差超过1的数的比较,结果是确定的,无法改变
只有相邻的数的比较结果才是可以构造的
即,对于数x来说(1<x<n),只有x和x-1,x和x+1这两次的结果是可供我们构造的
每一轮,每个数会和其他n-1个数进行比较,有n-1次赢的机会
由上可知,其中n-3次的输赢是一定的(特别的,对于1和n来说,n-2次输赢是一定的)
所以在计算d(k)=|r(1,k)-r(2,k)|时,这部分的输赢会抵消,我们无需处理
只需构造每个数和它相邻的数比较时的输赢
想到这里其实已经可以做出来了:
线性扫一遍给出的D序列,依次枚举每个数和它相邻的数电脑两轮可能给出的结果(四种)
再根据给出D序列中相应的值判断是否可以继续构造还是已经无解
下面是我的思路,也是对如何构造、如何判断的细节的考虑(实在是写的太绕了,前面已经看懂了建议自己想)
对于x和x+1之间的两轮比较:
('>'表示电脑给出的结果是x>x+1)
(r(x)=(a,b)表示:r(1,x)=a,r(2,x)=b)
比较结果 | (>,>) | (>,<) | (<,>) | (<,<) |
对r(x)的相应贡献 | (1,1) | (1,0) | (0,1) | (0,0) |
对r(x+1)的相应贡献 | (0,0) | (0,1) | (1,0) | (1,1) |
对d(x)的相应贡献d'(x) | 0 | 1 | 1 | 0 |
对d(x+1)的相应贡献d'(x+1) | 0 | 1 | 1 | 0 |
表格E-1
显然,我们可以构造出d'(x)=d'(x+1)=0或1
所以,对于数k来说(1<k<n)
k-1和k的比较可能会对d(k)有0~1的贡献
k和k+1的比较可能会对d(k)有0~1的贡献
d(k)=d'(k)(k-1和k比较)+d'(k)(k和k+1比较)
所以得到一个结论:如果不满足
1、0<=d(k)<=2,1<k<n
2、0<=d(k)<=1,k=1或n
则必无解(构造不出,输出'NO')
n很大,我们考虑线性的做法,从1到n依次构造
定义题目输入的数列为{a(n)}
由于1很特殊,只需考虑1和2之间的比较,所以我们根据a(1)的值就可以判断构造方法,具体如下:
d(1)=d'(1)=a(1)=0时,我们构造1和2比较结果为(>,>)或(<,<),对d(2)贡献d'(2)=0
d(1)=d'(1)=a(1)=1时,我们构造1和2比较结果为(>,<)或(<,>),对d(2)贡献d'(2)=1
d(1)=d'(1)=a(1)>1时,无解
再构造2~n-1中的数k
构造这些数时,构造的前一个数会对这个数的d有一定贡献d'
比如我们此时要构造2,而上述构造1的过程中已经对d(2)有贡献,我们称为初始贡献num
即,构造k时,我们要比较k和k+1,而num=d'(k)(k-1和k比较)
我们构造的方法大致和上述构造1的方法一样
区别在于:
d(1)=d'(1)=a(1)
d(k)=num+d'(k)=a(k)
所以我们对num进行分类讨论:
(1)num=0时,相当于之前的比较对现在的构造没有贡献
那就无视之前的构造,将现在的k当做第1个来构造,即重复上述对1的构造方法
(2)num=1时
可知我们之前的构造是(>,<)或(<,>)
导致我们现在构造的时候,r(k)的初始值是(0,1)或(1,0)
(当然具体是构造了前者还是后者无所谓,因为由于之后算d(k)时取绝对值,两轮是等价的)
不妨取r(k)的值为(0,1)
我们现在构造k和k+1的比较结果,参照表格E-1得下表:
比较结果 | (>,>) | (>,<) | (<,>) | (<,<) |
对r(k)的相应贡献 | (1,1) | (1,0) | (0,1) | (0,0) |
最终的r(k) | (1,2) | (1,1) | (0,2) | (0,1) |
最终的d(k) | 1 | 0 | 2 | 1 |
对d(k+1)的相应贡献d'(k+1) | 0 | 1 | 1 | 0 |
表格E-2
我们就根据a(k)的值进行相应构造,并更新num=d'(k+1)以构造下一个数
最终,和1一样,n也是一个特殊的数:
d(n)=d'(n)=a(n)
而构造n-1时我们得到的num即为d'(n)
最后验证一下a(n)是否符合条件即可
思路严谨地书面表达出来之后比较绕,建议在纸上动笔打草稿以更快理解
1 #include<cstdio> 2 using namespace std; 3 4 const int N=1000100; 5 int n,a[N],flag,num; 6 7 int main() 8 { 9 scanf("%d",&n); 10 for (int i=1; i<=n; ++i) scanf("%d",&a[i]); 11 for (int i=1; i<n; ++i) 12 { 13 if (a[i]>2) {flag=1; break;} //对得到的结论的应用 14 if (!num) //对1的构造并入num=0的情况 15 { 16 if (a[i]==0) num=0; 17 if (a[i]==1) num=1; 18 if (a[i]==2) {flag=1; break;} 19 } 20 else //程序的主体实际只是一个分类讨论 21 { 22 if (a[i]==0) num=1; 23 if (a[i]==1) num=0; 24 if (a[i]==2) num=1; 25 } 26 } 27 if (num!=a[n]) flag=1; //特判一下a(n)是否符合 28 if (a[n]>2) flag=1; 29 if (flag) printf("NO"); else printf("YES"); 30 return 0; 31 }
【G. Mobile Robot】
设给定的初始数列是{ai},我们要将机器人移动到{bi}
此时ans=max{|ai-bi|},i=1,2,...,n
令ci=ai-bi,i=1,2,...,n,则ans=max{|ci|}
由于d=|bi-b(i-1)|是固定的,那么{bi}中任意两项的差值都是固定的
所以相当于我们更改了b1的值,整个数列{bi}都会跟着改变,ci也会跟着改变
(将b1+=k(不妨k>0)时,想象一下整个{bi}都移动了k,{ci}都减小了k)
由于ans=max{|ci|},设{ci}中最大值为c_max,最小值为c_min(整个{ci}的值的更改是同步的,所以最大最小的两项也是固定的)
其实ans=max{|c_max|,|c_min|},而c_max-c_min的值是固定的
显然c_max=-c_min时最优,此时|c_max|=|c_min|
所以我们先随便写一个{bi},算出{ci},找到c_max和c_min这两项
算出c_max=-c_min成立时ans=max{|c_max|,|c_min|}=|c_max|=|c_min|
注意:
1、因为d=|bi-b(i-1)|的计算是带绝对值的,所以要分{bi}单调增和单调减两种情况讨论
2、数实在太大了,输出实数时精度不够用,反正最后输出的一位小数不是.0就是.5,就干脆手动输出实数了
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #define LL long long 5 using namespace std; 6 7 const int N=1000100; 8 LL n,d,a[N],num,minn,maxx,pos,ans1,ans2; 9 10 int main() 11 { 12 scanf("%lld%lld",&n,&d); 13 for (int i=1; i<=n; ++i) scanf("%lld",&a[i]); 14 15 num=a[1]; minn=0; maxx=0; //就随便b1=a1算出一个{bi} 16 for (int i=2; i<=n; ++i) 17 { 18 num+=d; //num相当于就是bi 19 minn=min(minn,a[i]-num); //找c_min 20 maxx=max(maxx,a[i]-num); //找c_max 21 } 22 ans1=maxx-minn; 23 24 num=a[1]; minn=0; maxx=0; 25 for (int i=2; i<=n; ++i) 26 { 27 num-=d; //{bi}单调减的情况再做一遍 28 minn=min(minn,a[i]-num); 29 maxx=max(maxx,a[i]-num); 30 } 31 ans2=maxx-minn; 32 33 if (ans2<ans1) ans1=ans2; //挑小的一个 34 if (ans1%2) printf("%lld.5",ans1/2); else printf("%lld.0",ans1/2); 35 return 0; 36 }