2020牛客暑期多校第三场

C.Operation-Love

按照顺序依次计算两点之间的距离

如果(a,b)长度为6,(b,c)长度为1,说明(a,b)是拇指的线段(指向指尖)

如果(a,b)长度为6,(b,c)长度为9,说明(b,a)是拇指的线段(指向指尖)

如果是左手,那么其他所有点都在拇指线段的左侧,反之则在右侧。

判断点在线段左侧还是右侧可以通过叉乘的方法。

假设线段为(x1,y1)->(x2,y2),点坐标为(x0,y0)

那么计算(x2-x1,y2-y1)×(x0-x1,y0-y1)的值,<0为右侧,>0为左侧

#include<bits/stdc++.h>
#define debug(x) cerr<<#x<<" : "<<x<<endl;
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN=2e3+5;
const double eps=1e-4;

class Point {
public:
    double x,y;
    bool operator !=(Point &P)
    {
        return fabs(x-P.x)>eps && fabs(y-P.y)>eps;
    }
    Point(double a=0,double b=0):x(a),y(b){}
};
class Line {
public:
    bool ifk;
    double dis;
    double k,b;
    //求过两点的直线
    void mkline (Point A,Point B) {
        dis=sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y));
        ifk=true;k=b=0;
        if (A.x==B.x) {
            ifk=0;
            b=A.x;
        } else {
            if (A.y==B.y) {
                k=0;
                b=(A.x+B.x)/2;
            } else {
                k=(B.y-A.y)/(B.x-A.x);
                b=A.y-k*A.x;
            }
        }
    }
    //求两点中垂线
    void mkbisector (Point A,Point B) {
        ifk=true;k=b=0;
        if (A.x==B.x) {
            k=0;
            b=(A.y+B.y)/2;
        } else if (A.y==B.y) {
                ifk=0;
                b=(A.x+B.x)/2;
            } else {
                k=-1/(B.y-A.y)*(B.x-A.x);
                b=(A.y+B.y)/2-k*(A.x+B.x)/2;
            }
    }
    bool operator == (Line &T) {
        return (ifk==T.ifk) && fabs(k-T.k)<eps && fabs(b-T.b)<eps;
    }
};

Point p[30];

Line l[30];

int T;
int main()
{
    cin>>T;
    while(T--)
    {
        for (int i=1;i<=20;i++)
        {
            cin>>p[i].x>>p[i].y;
        }
        p[21]=p[1];
        for (int i=1;i<=20;i++)
        {
            l[i].mkline(p[i],p[i+1]);
        }
        l[21]=l[1];
        Point a,b,c;
        for (int i=1;i<=20;i++)
        {
            if (fabs(l[i].dis-6)<eps && fabs(l[i+1].dis-1)<eps)
            {
                a=p[i],b=p[i+1];
            }
            if (fabs(l[i].dis-6)<eps && fabs(l[i+1].dis-9)<eps)
            {
                a=p[i+1],b=p[i];
            }
        }
        for (int i=1;i<=20;i++)
            if (p[i]!=a && p[i]!=b)
            {
                c=p[i];
                break;
            }
        double p=b.x-a.x,q=b.y-a.y,w=c.x-a.x,o=c.y-a.y;
        if (p*o-q*w<0) printf("right\n");
        else printf("left\n");
    }
    return 0;
}

E.Two-Matchings

本身是一个图论问题,要使得每个点都连上两条边且边的权值和最小,权值为两个数之差的绝对值。可以把n个数从小到大排序,把问题变成在一列有序数上连线的问题。

首先考虑到n=4或n=6的情况,可以发现此时答案是固定的,也就是(A[n]-A[1])*2,而对于n>=8的情况,总是可以拆成若干个长度为4或6的分块,并使答案减少分块连接处的两数之差。也就是dp[n]总是从dp[n-4]或dp[n-6]减去连接处的权值后转移过来,这样就可以通过dp计算答案了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=2e5+5;

const ll INF=0x3f3f3f3f3f3f3f3f;

int T,n,m,A[N];

ll dp[N];

int main()
{
    cin>>T;
    while(T--)
    {
        cin>>n;
        for (int i=1;i<=n;i++)
        {
            scanf("%d",&A[i]);
            dp[i]=INF;
        }
        sort(A+1,A+1+n);
        dp[0]=0;
        for (int i=4;i<=n;i+=2)
        {
            if (i>=4) dp[i]=min(dp[i],dp[i-4]+(A[i]-A[i-3])*2);
            if (i>=6) dp[i]=min(dp[i],dp[i-6]+(A[i]-A[i-5])*2);
        }
        printf("%lld\n",dp[n]);
    }
    return 0;
}

F.Fraction-Construction-Problem

如果a,b不互质那么答案比较显然,取$\frac{a}{b}$约分之后的分母即可。

考虑a、b互质的情况,把原式转换成$\frac{cf-ed}{df}=\frac{a}{b}$

如果b自身是质数或者1,那么d、f中至少有一个大于等于b,不符合条件。

如果b不是质数且不是1,那么d总能分解成两个互质因子,令b的两个互质因子分别为d、f,那么由扩展欧几里得算法可知$cf-ed=a$一定有整数解。将d、f代入后求正整数解即可。

为了求d的互质因子,在素数筛中可以加入pfactor[i]数组记录i的第一个质因数。将第一个质因数全部取出后,分离出的两个数一定互质。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;

const int MAXN=2e6+5;

int T,n,m,a,b;

int vis[MAXN],prime[MAXN],num_p;
int pfactor[MAXN];
void getPrime()
{
    for (int i=2;i<MAXN;i++)
    {
        if (!vis[i]) prime[++num_p]=i;
        for (int j=1;j<=num_p;j++)
        {
            if (i*prime[j]>=MAXN) break;
            pfactor[i*prime[j]]=prime[j];
            vis[i*prime[j]]=1;
            if (i%prime[j]==0) break;
        }
    }
}

void Exgcd(ll a, ll b, ll &x, ll &y) {
    if (!b) x = 1, y = 3;
    else Exgcd(b, a % b, y, x), y -= a / b * x;
}

void solve()
{
    cin>>a>>b;
    ll c,d,e,f;
    int g=__gcd(a,b);
    if (g!=1)
    {
        d=f=b/g;
        e=1,c=a/g+1;
        printf("%lld %lld %lld %lld\n",c,d,e,f);
        return;
    }
    a/=g,b/=g;
    if (pfactor[b]!=0)
    {
        int d=1,f=b;
        while(f%pfactor[b]==0) f/=pfactor[b],d*=pfactor[b];
        if (f!=1) {
            Exgcd(f,d,c,e);
            e=-e;
            while(c<=0 || e<=0)
                c+=d,e+=f;
            c*=a,e*=a;
            printf("%lld %lld %lld %lld\n",c,d,e,f);
            return;
        }
    }
    printf("-1 -1 -1 -1\n");
}
int main()
{
    cin>>T;
    getPrime();
    while(T--) solve();
    return 0;
}

G.Operating-on-a-Graph

其实就是并查集,但是需要一些技巧。

两个重点,一是启发式合并,将b合并入a时,若b中元素较多,则直接交换a、b中的元素。

二是合并前清空原有的元素,合并后只保留新增的元素。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;

const int MAXN=1e6+5;

int T,n,m,q,t,u,v;

int FA[MAXN];
vector<int>G[MAXN];
vector<int>group[MAXN];

int fnd(int x)
{
    if (FA[x]==x) return x;
    else return FA[x]=fnd(FA[x]);
}
void solve()
{
    cin>>n>>m;
    for (int i=0;i<n;i++)
    {
        G[i].clear();
        group[i].clear();
        FA[i]=i;
        group[i].push_back(i);
    }
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    cin>>q;
    while(q--)
    {
        scanf("%d",&t);
        if (fnd(t)!=t) continue;
        vector<int>now;
        for (auto h:group[t])
            for (auto to:G[h])
            {
                now.push_back(fnd(to));
            }
        group[t].clear();
        for (auto x:now)
        {
            if (fnd(x)==t) continue;
            FA[x]=t;
            if (group[x].size()>group[t].size()) swap(group[x],group[t]);
            for (auto tt:group[x]) group[t].push_back(tt);
            group[x].clear();
        }
    }
    for (int i=0;i<n;i++) printf("%d ",fnd(i));
    printf("\n");
}
int main()
{
    cin>>T;
    while(T--) solve();
    return 0;
}

D.Points Construction Problem

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5;

int T,n,m;

bool check(int x) {
    int sq=sqrt(x);
    if (x==sq*sq+1 || x==sq*(sq+1)+1) return 0;
    else return 1;
}

int getMn(int x) {
    int sq=sqrt(x);
    if (sq*sq==x) return sq*4;
    if (sq*(sq+1)>=x) return (sq+sq+1)*2;
    return (sq+1)*4;
}
int main() {
    cin>>T;
    while(T--) {
        scanf("%d%d",&n,&m);
        int mx=4*n,mn=getMn(n);
        if (m<mn || m>mx || m%2!=0) {
            printf("No\n");
            continue;
        }
        printf("Yes\n");
        int now=mn,cnt=n;
        while(now<m) {
            if (check(cnt)) {
                cnt--;
                now+=4;
            }
            else {
                cnt--;
                now+=2;
            }
        }
        int fix=0;
        if (now>m) {
            fix=1;
            printf("0 1\n");
        }
        int x=sqrt(cnt-1)+1,t=0;
        for (int i=1;i<=x;i++){
            for (int j=1;j<=x;j++){
                printf("%d %d\n",i,j);
                t++;
                if (t==cnt) break;
            }
            if (t==cnt) break;
        }
        for (int i=100;i<=100+n-cnt-1-fix;i++)
            printf("%d %d\n",i,i);
    }
    return 0;
}