AtCoder-ABC-181(A-F)

A

奇数输出Black,偶数输出White。

B

求多个连续整数区间和。

C

判断点C是否在直线AB上,可以通过判断AB向量和AC向量的叉积是否为0,叉积公式为$x_1y_2-x_2y_1$,大于0说明AC向量在AB向量顺时针方向,小于0是逆时针方向,等于0则共线。

D

1e5的数字串,问能否重新调整位置使得这个数可以整除8。分类讨论,因为1000肯定可以被8整除,所以只要枚举后三位能被8整除的数,判断原来的数能否组成这个三位数。

注意: 枚举要从三位数开始枚举,也就是104,而不是8,枚举8的话另外两位不一定是0,所以如果要枚举8的话,其实要枚举的是008,所以原数字里需要两个0。如果从104开始枚举,例如1008,1016这种数字也会被被check 到,因为会枚举到800,800可以,008显然也可以,同理也会枚举到160,所以016也可以。

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
char s[N];
int dig[10];
bool check(int x){
    int temp[10]={0};
    int cnt=3;
    while(x){
        cnt--;
        temp[x%10]++;
        x/=10;
    }
    temp[0]+=cnt;
    for(int i=0;i<10;i++){
        if(dig[i]<temp[i]){
            return false;
        }
    }
    return true;
}
int main(){
    scanf("%s",s);
    int n=strlen(s);
    if(n==1){
        if(s[0]=='8'){
            printf("Yes\n");
        }else{
            printf("No\n");
        }
    }else if(n==2){
        if(((s[0]-'0')*10+(s[1]-'0'))%8==0 || ((s[1]-'0')*10+(s[0]-'0'))%8==0){
            printf("Yes\n");
        }else{
            printf("No\n");
        }
    }else{
        for(int i=0;i<n;i++){
            dig[s[i]-'0']++;
        }
        for(int i=8;i<1000;i+=8){
            if(check(i)){
                printf("Yes\n");
                return 0;
            }
        }
        printf("No\n");
    }
}

E

从m个数中选择一个插入到另外n个数(n为奇数),然后两两配对,使得差的和最小。

首先n个数排序,然后枚举m个数中的每一个,二分查找插入的位置,插入之后会影响小范围的答案统计,而大范围的不变,所以可以先正着和倒着分别求一次差的前缀和和后缀和,然后分类讨论插入的位置,更新答案即可。

一开始可以先把所有边界情况详细分类讨论,然后再考虑合并。

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
int n,m,h[N],w[N];
int l[N],r[N];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&h[i]);
    }
    for(int i=1;i<=m;i++){
        scanf("%d",&w[i]);
    }
    sort(h+1,h+1+n);
    for(int i=1;i<=n;i++){
        l[i]=l[i-1];
        if(i%2==0){
            l[i]+=h[i]-h[i-1];
        }
    }
    for(int i=n;i>=1;i--){
        r[i]=r[i+1];
        if((n-i)%2==1){
            r[i]+=h[i+1]-h[i];
        }
    }
    int ans=INT_MAX;
    for(int i=1;i<=m;i++){
        int k=lower_bound(h+1,h+1+n,w[i])-h;
        if(k%2==1){
            ans=min(ans,l[k-1]+h[k]-w[i]+r[k+1]);
        }else{
            ans=min(ans,l[k-2]+w[i]-h[k-1]+r[k]);
        }
    }
    printf("%d\n",ans);
    return 0;
}

F

y=100和y=-100两条直线之间有一些钉子,求一个最大的环能够从左边走到右边而不会碰到钉子和直线。

因为答案具有单调性,所以可以二分答案,然后考虑一个问题:两个钉子如果距离小于2r,说明环不可能从这两个钉子之间通过,所以可以把这两个钉子看成一个整体,所以用到了并查集,对于每个半径,枚举所有钉子对,用并查集维护,也包括了上下两条直线。

最后判断如果上下两条直线已经属于同一个并查集,则这个半径的环无法通过。

注意: 一开始写成当并查集数量等于1时,无法通过,其实不止这种情况,有可能上下直线连成一起,但是还有其他的钉子组成了其他的并查集。

#include <bits/stdc++.h>
using namespace std;
const double eps=1e-8;
int n;
double x[105],y[105];
int p[105];
void init(){
    for(int i=1;i<=n+2;i++){
        p[i]=i;
    }
}
int find(int x){
    return x==p[x]?p[x]:find(p[x]);
}
void unit(int i,int j){
    int fa=find(i);
    int fb=find(j);
    p[fa]=fb;
}
double dis2(int i,int j){
    return (x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
}
bool check(double a){
    init();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i==j){
                continue;
            }
            if(dis2(i,j)-4*a*a<=eps){
                unit(i,j);
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(100-y[i]-2*a<=eps){
            unit(i,n+1);
        }
        if(y[i]+100-2*a<=eps){
            unit(i,n+2);
        }
    }
    return find(n+1)!=find(n+2);
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lf%lf",&x[i],&y[i]);
    }
    double l=0,r=100;
    double ans;
    while(l+eps<=r){
        double mid=(l+r)/2;
        if(check(mid)){
            ans=mid;
            l=mid;
        }else{
            r=mid;
        }
    }
    printf("%lf\n",ans);
    return 0;
}
目录