The 2024 International Collegiate Programming Contest in Hubei Province, China

Preface

感觉好久没训练了,这周末又要出战西安,只好找个平时的晚上抽空训练一场

这场题本身质量还是不错的,但由于徐神被模拟题关了一整场,我前期被一个分类讨论写的心态爆炸

导致最后一个medium的计数题没做出来,然后一个medium~hard的D题转化和性质基本都挖掘完了,最后没想到暴力增量法维护贡献(不过本来也没机时写了)


A. Long Live

签到,不难发现令\(a=1\)一定最优

#include <bits/stdc++.h>

int main() {
    std::ios::sync_with_stdio(false);
    int T; std::cin >> T;
    while(T--) {
        int64_t x, y;
        std::cin >> x >> y;
        std::cout << "1 " << x * y / (std::__gcd(x, y) * std::__gcd(x, y)) << char(10);
    }
    return 0;
}

B. Nana Likes Polygons

签到,不难发现面积最小的凸包一定是三角形,直接\(O(n^3)\)枚举顶点即可

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

const int N = 105;
const int INF = (int)1e9+5;

int t, n;
struct Pt{
    int x, y;
    Pt operator-(Pt b)const{return Pt{x-b.x, y-b.y};}
    int crs(Pt b){return x*b.y-y*b.x;}
}pt[N];
int tcross(Pt a, Pt b, Pt c){return abs((b-a).crs(c-a));}


signed main(){
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> t;
    while (t--){
        cin >> n;
        int ans = INF;
        for (int i=1; i<=n; ++i) cin >> pt[i].x >> pt[i].y;
        for (int i=1; i<=n; ++i) for (int j=i+1; j<=n; ++j) for (int k=j; k<=n; ++k){
            int res = tcross(pt[i], pt[j], pt[k]);
            if (res>0) ans = min(ans, res);
        }
        if (ans<INF) cout << ans*0.5L << '\n';
        else cout << "-1\n";
    }
    return 0;
}

C. Lili Likes Polygons

看起来是个很神秘的Flow题,没时间补了先弃疗


D. MACARON Likes Happy Endings

纯纯的套路题,首先这种分割成若干段的状态肯定是\(f_{i,j}\),表示把前\(i\)个数分成\(j\)段,且钦定\(i\)为某段的末尾的最小代价

转移的话就是枚举转移点\(k\),则有:\(f_{i,j}=\min_\limits{1\le k\le i} f_{k-1,j-1}+w(k,i)\),其中\(w(l,r)\)就是区间\([l,r]\)sadness

然后祁神在比赛中证出了这个DP是有决策单调性的,即先固定\(j\)后,每个位置的最优决策点单调不减

赛中就一直在思考怎么快速维护\(w(l,r)\)的值,后面也想到了增量法但没想到直接暴力上复杂度就是对的

考虑类似莫队的处理,我们维护某个区间的端点\([l,r]\)以及贡献\(w(l,r)\),端点移动时贡献的变化量可以用桶来快速维护

最后在外面套一个经典的整体二分维护决策单调性的壳即可,对于两个端点单独分析移动次数后不难发现这部分的复杂度是正确的

总复杂度\(O(nk\log n)\)

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,S=2e6+5,INF=1e18;
int n,k,d,x,pfx[N],f[N],g[N],bkt[S],L,R,ret;
inline void move(CI l,CI r)
{
	while (L>l) ++bkt[pfx[--L]],ret+=bkt[pfx[L-1]^d];
	while (L<l) ret-=bkt[pfx[L-1]^d],--bkt[pfx[L++]];
	while (R<r)
	{
		ret+=bkt[pfx[R+1]^d];
		if ((pfx[R+1]^pfx[L-1])==d) ++ret;
		++bkt[pfx[++R]];
	}
	while (R>r)
	{
		--bkt[pfx[R--]];
		if ((pfx[R+1]^pfx[L-1])==d) --ret;
		ret-=bkt[pfx[R+1]^d];
	}
}
inline void solve(CI pl,CI pr,CI l,CI r)
{
	if (l>r) return; int mid=l+r>>1,pos=-1;
	for (RI i=pl;i<=min(pr,mid);++i)
	{
		move(i,mid); int tmp=ret+g[i-1];
		if (tmp<f[mid]) f[mid]=tmp,pos=i;
	}
	solve(pl,pos,l,mid-1); solve(pos,pr,mid+1,r);
}
signed main()
{
	RI i,j; for (scanf("%lld%lld%lld",&n,&k,&d),i=1;i<=n;++i)
	scanf("%lld",&x),pfx[i]=pfx[i-1]^x; L=1; R=0; ret=0;
	/*static int cnt[S]; for (i=1;i<=n;++i) for (j=i;j<=n;++j)
	{
		move(i,j); ++cnt[pfx[i-1]]; int tmp=0;
		for (RI k=i;k<=j;++k) tmp+=cnt[pfx[k]^d],++cnt[pfx[k]];
		for (RI k=i;k<=j;++k) cnt[pfx[k]]=0; cnt[pfx[i-1]]=0;
		if (ret!=tmp) printf("[%lld,%lld] %lld %lld\n",i,j,ret,tmp);
	}*/
	for (i=1;i<=n;++i) move(1,i),f[i]=g[i]=ret;
	for (j=2;j<=k;++j)
	{
		for (i=1;i<=n;++i) f[i]=INF;
		for (solve(1,n,1,n),i=1;i<=n;++i) g[i]=f[i];
	}
	return printf("%lld",f[n]),0;
}

E. Spicy or Grilled?

签到,就我个人而言肯定是板烧大于麦辣的说

#include<cstdio>
#include<iostream>
using namespace std;
int t,n,x,a,b;
int main()
{
    for (scanf("%d",&t);t;--t)
    scanf("%d%d%d%d",&n,&x,&a,&b),printf("%d\n",x*b+(n-x)*a);
    return 0;
}

F. Enchanted

诈骗题,看到\(q\le 30\)后就知道是个一眼题了,但由于被祁神骗了以为有强制在线,就没去写离线建树的写法,最后又被卡空间又被卡时间搞到结束前5min才卡过去

不难发现我们把每个数看作\(2^{a_i}\)后,只要求一个区间和那么前两个询问操作都可以暴力求解了

剩下的单点修改和回退版本,可离线的话就是把转移的树状图建出来然后DFS并回溯即可

在线的做法就直接上主席树就完了,也没啥技巧,两种做法的时间复杂度都是一个\(\log\)

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
using namespace std;
const int N=1e6+5,mod=1e9+7;
int n,m,A,p,q,a[N],opt,l,r,k; signed rt[N];
inline int rnd(void)
{
    return A=(7LL*A+13LL)%19260817;
}
class Segment_Tree
{
    public:
        struct segment
        {
            signed ls,rs;
            int sum;
        }O[N*50]; int idx;
    public:
        #define TN signed l=1,signed r=n
        #define LS l,mid
        #define RS mid+1,r
        inline void modify(signed lst,signed& now,signed pos,int mv,TN)
        {
            O[now=++idx]=O[lst]; if (l==r) return (void)(O[now].sum=mv); signed mid=l+r>>1;
            if (pos<=mid) modify(O[lst].ls,O[now].ls,pos,mv,LS); else modify(O[lst].rs,O[now].rs,pos,mv,RS);
            O[now].sum=O[O[now].ls].sum+O[O[now].rs].sum;
        }
        inline int query(signed now,signed beg,signed end,TN)
        {
            if (!now) return 0LL; if (beg<=l&&r<=end) return O[now].sum; signed mid=l+r>>1; int ret=0;
            if (beg<=mid) ret+=query(O[now].ls,beg,end,LS); if (end>mid) ret+=query(O[now].rs,beg,end,RS); return ret;
        }
        #undef TN
        #undef LS
        #undef RS
}SEG;
signed main()
{
    //std::cout << sizeof(SEG.O) / 1024.L / 1024.L << char(10);
    RI i,j; scanf("%lld%lld%lld%lld%lld",&n,&m,&A,&p,&q);
    for (i=1;i<=n;++i) a[i]=rnd()%q+1,SEG.modify(rt[0],rt[0],signed(i),1LL<<a[i]);
    //for (i=1;i<=n;++i) printf("%lld%c",a[i]," \n"[i==n]);
    for (i=1;i<=m;++i)
    {
        opt=rnd()%p+1;
        if (opt==1)
        {
            rt[i]=rt[i-1]; l=rnd()%n+1; r=rnd()%n+1;
            if (l>r) swap(l,r);
            int sum=SEG.query(rt[i],l,r),hb=0;
            //printf("sum=%lld\n", sum);
            for (j=60;j>=0;--j) if ((sum>>j)&1) { hb=j; break; }
            printf("%lld\n",hb);
        } else if (opt==2)
        {
            rt[i]=rt[i-1]; l=rnd()%n+1; r=rnd()%n+1; k=rnd()%q+1;
            if (l>r) swap(l,r);
            int sum=SEG.query(rt[i],l,r),ans=0;
            //printf("sum=%lld k=%lld\n", sum,k);
            for (j=k;j<=60;++j)
            {
                if ((sum>>j)&1) (ans+=(1LL<<(j+1)))%=mod; else break;
            }
            printf("%lld\n",ans);
        } else if (opt==3)
        {
            l=rnd()%n+1; k=rnd()%q+1;
            SEG.modify(rt[i-1],rt[i],l,(1LL<<k));
        } else rt[i]=rt[rnd()%i];
    }
    return 0;
}

G. Genshin Impact Startup Forbidden II

唉原神,唉围棋,据说就是个大力模拟题

但徐神写了三个version的代码,一个TLE飞了,一个过不了样例,最后修修补补终于淦过了这个题

由于我连围棋的规则都不知道所以就没碰这个题了QWQ

#include <bits/stdc++.h>

struct Stone { int id = 0, side = 0; };

Stone go[19][19];

int fa[500005], qi[500005];
std::vector<std::pair<int, int>> s[500005];
inline int father(int a) {
    if(a == fa[a]) return a;
    return fa[a] = father(fa[a]);
}

inline void unionn(int a, int b) {
    a = father(a), b = father(b);
    if(a == b) return ;
    if(s[a].size() > s[b].size()) std::swap(a, b);
    fa[a] = b; qi[b] += qi[a];
    for(auto a: s[a]) s[b].push_back(a);
    s[a].clear();
};

inline std::vector<std::pair<int, int>> nb(int x, int y) {
    constexpr int D[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    auto res = std::vector<std::pair<int, int>> {};
    res.reserve(4);
    for(auto &[dx, dy]: D) {
        int nx = x + dx, ny = y + dy;
        if(0 <= nx && nx < 19 && 0 <= ny && ny < 19) res.emplace_back(nx, ny);
    }
    return res;
}

inline int& get_qi(int x, int y) { return qi[father(go[x][y].id)]; }
inline int side(int x, int y) { return go[x][y].side; }

int remove(int i) {
    for(auto [x, y]: s[i]) {
        for(auto [nx, ny]: nb(x, y)) if(side(nx, ny))
            get_qi(nx, ny) += 1;
        go[x][y] = {0, 0};
    }
    return s[i].size();
}

int remove(int x, int y) { return remove(father(go[x][y].id)); }

auto main() -> int {
    std::ios::sync_with_stdio(false);
    int m, player = 1;
    std::cin >> m;
    for(int i = 1; i <= m; ++i) fa[i] = i;
    for(int i = 1; i <= m; ++i, player = -player) {
        int x, y; std::cin >> x >> y;
        x -= 1; y -= 1;
        s[i].emplace_back(x, y);

        int ans1 = 0, ans2 = 0;
        go[x][y] = { .id = i, .side = player };

        auto NB = nb(x, y);

        for(auto [nx, ny]: NB) if(side(nx, ny)) get_qi(nx, ny) -= 1;
        for(auto [nx, ny]: NB) if(side(nx, ny) == -player && get_qi(nx, ny) == 0) ans1 += remove(nx, ny);
        qi[i] = 0;
        for(auto [nx, ny]: NB) if(side(nx, ny) == 0) qi[i] += 1;
        for(auto [nx, ny]: NB) if(side(nx, ny) == player) unionn(i, go[nx][ny].id);
        if(get_qi(x, y) == 0) ans2 = remove(x, y);

        if(player > 0) std::swap(ans1, ans2);
        std::cout << ans1 << ' ' << ans2 << char(10);

        // for(int i = 0; i < 5; ++i) for(int j = 0; j < 5; ++j) {
        //     switch(go[i][j].side) {
        //         case 1: std::cout << qi[father(go[i][j].id)]; break;
        //         case -1: std::cout << 'X'; break;
        //         case 0: std::cout << '_';
        //     }
        //     if(j == 4) std::cout << "\n";
        // }
        // std::cout <<"----------\n";
    }
    return 0;
}

H. Genshin Impact Startup Forbidden III

很套路的题,由于有鱼的格子数量只有\(\le 10\)个,然后这些格子里鱼的数量\(\in\{0,1,2,3\}\),因此直接状压状态数就是\(4^{10}\)可以接受

然后就是写个大力BFS找到从初始状态到全\(0\)的最短路即可,注意最近CF机子慢的离谱,需要加上各种奇妙优化才能通过

#include<cstdio>
#include<iostream>
#include<vector>
#include<map>
#include<queue>
#include<utility>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=15,S=(1<<20)+5,dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
int n,m,k,x[N],y[N],z[N],dist[S]; map <pi,vector <int>> rst; vector <vector <int>> state;
int main()
{
    RI i,j; scanf("%d%d%d",&n,&m,&k); vector <int> st;
    for (i=0;i<k;++i) scanf("%d%d%d",&x[i],&y[i],&z[i]),st.push_back(z[i]);
    for (i=0;i<k;++i)
    {
        rst[pi(x[i],y[i])].push_back(i);
        for (j=0;j<4;++j)
        {
            int nx=x[i]+dx[j],ny=y[i]+dy[j];
            if (nx<1||nx>n||ny<1||ny>m) continue;
            rst[pi(nx,ny)].push_back(i);
        }
    }
    for (auto [it,vec]:rst) state.push_back(vec);
    auto H=[&](vector <int>& vec)
    {
        int v=0; for (auto x:vec) v=v*4+x; return v;
    };
    memset(dist,-1,sizeof(dist));
    queue <vector <int>> q; q.push(st); dist[H(st)]=0;
    while (!q.empty())
    {
        auto cur=q.front(); q.pop(); int hc=H(cur);
        for (auto &vec:state)
        {
            auto nxt=cur;
            for (auto &x:vec) if (nxt[x]>0) --nxt[x];
            int hn=H(nxt); if (~dist[hn]) continue;
            dist[hn]=dist[hc]+1; q.push(nxt);
        }
    }
    return printf("%d",dist[0]),0;
}

I. Colorful Tree

套路题,话说怎么又是动态维护直径,几天后的武汉邀请赛也有一个直径题,难道是去年杭州站带起的风向?

首先注意到一个点变黑之后就永远不会变回来了,因此我们可以对每个点维护出它第一次变黑的时间

具体方法就是把染色操作倒着考虑,每次用树剖+线段树实现区间赋值操作,修改的值就是当前操作的编号,最后每个点的点权就是所求的时间\(a_i\),同理\(a_i-1\)​就是每个点最后保持白色的时间

定义一条边\((x,y)\)变黑的时间为\(\max(a_x,a_y)\),同理倒过来做时它变白的时间为\(\min(a_x-1,a_y-1)\)

首先考虑黑色节点构成的森林,不难发现我们只需要按照时序将每条边加入,用并查集维护连通块内的直径端点即可

白色节点构成的森林同理,直接倒过来做一遍即可,总复杂度\(O(n\log n)\)

#include<cstdio>
#include<iostream>
#include<vector>
#include<utility>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
int t,n,q,x[N],y[N],u[N],v[N],ans[N],fa[N],ret;
vector <int> E[N]; vector <pi> B[N],W[N];
class Segment_Tree
{
    private:
        int val[N<<2],tag[N<<2];
        inline void apply(CI now,CI mv)
        {
            val[now]=tag[now]=mv;
        }
        inline void pushdown(CI now)
        {
            if (tag[now]) apply(now<<1,tag[now]),apply(now<<1|1,tag[now]),tag[now]=0;
        }
    public:
        #define TN CI now=1,CI l=1,CI r=N
        #define LS now<<1,l,mid
        #define RS now<<1|1,mid+1,r
        inline void build(TN)
        {
            val[now]=q+1; tag[now]=0; if (l==r) return;
            int mid=l+r>>1; build(LS); build(RS);
        }
        inline void modify(CI beg,CI end,CI mv,TN)
        {
            if (beg<=l&&r<=end) return apply(now,mv); int mid=l+r>>1; pushdown(now);
            if (beg<=mid) modify(beg,end,mv,LS); if (end>mid) modify(beg,end,mv,RS);
        }
        inline int query(CI pos,TN)
        {
            if (l==r) return val[now]; int mid=l+r>>1; pushdown(now);
            if (pos<=mid) return query(pos,LS); else return query(pos,RS);
        }
        #undef TN
        #undef LS
        #undef RS
}SEG;
namespace Heavy_Division
{
    int idx,top[N],dep[N],sz[N],dfn[N],anc[N],son[N];
    inline void DFS1(CI now=1,CI fa=0)
    {
        sz[now]=1; dep[now]=dep[fa]+1; anc[now]=fa; son[now]=0;
        for (auto to:E[now]) if (to!=fa)
        {
            DFS1(to,now); sz[now]+=sz[to];
            if (sz[to]>sz[son[now]]) son[now]=to;
        }
    }
    inline void DFS2(CI now=1,CI tf=1)
    {
        dfn[now]=++idx; top[now]=tf;
        if (son[now]) DFS2(son[now],tf);
        for (int to:E[now]) if (to!=anc[now]&&to!=son[now]) DFS2(to,to);
    }
    inline void modify(int x,int y,CI mv)
    {
        while (top[x]!=top[y])
        {
            if (dep[top[x]]<dep[top[y]]) swap(x,y);
            SEG.modify(dfn[top[x]],dfn[x],mv);
            x=anc[top[x]];
        }
        if (dep[x]<dep[y]) swap(x,y);
        SEG.modify(dfn[y],dfn[x],mv);
    }
    inline int LCA(int x,int y)
    {
        while (top[x]!=top[y])
        {
            if (dep[top[x]]<dep[top[y]]) swap(x,y);
            x=anc[top[x]];
        }
        if (dep[x]<dep[y]) swap(x,y);
        return y;
    }
};
using namespace Heavy_Division;
inline int getfa(CI x)
{
    return x!=fa[x]?fa[x]=getfa(fa[x]):x;
}
inline int calc(CI x,CI y)
{
    return dep[x]+dep[y]-dep[LCA(x,y)]*2;
}
inline void merge(int x,int y)
{
    x=getfa(x); y=getfa(y); fa[y]=x;
    int ux=u[x],vx=v[x],uy=u[y],vy=v[y];
    if (calc(uy,vy)>calc(u[x],v[x])) u[x]=uy,v[x]=vy;
    if (calc(ux,uy)>calc(u[x],v[x])) u[x]=ux,v[x]=uy;
    if (calc(ux,vy)>calc(u[x],v[x])) u[x]=ux,v[x]=vy;
    if (calc(vx,uy)>calc(u[x],v[x])) u[x]=vx,v[x]=uy;
    if (calc(vx,vy)>calc(u[x],v[x])) u[x]=vx,v[x]=vy;
    ret=max(ret,calc(u[x],v[x]));
}
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        RI i; for (scanf("%d%d",&n,&q),i=1;i<n;++i)
        scanf("%d%d",&x[i],&y[i]),E[x[i]].push_back(y[i]),E[y[i]].push_back(x[i]);
        for (DFS1(),DFS2(),i=1;i<=q;++i) scanf("%d%d",&u[i],&v[i]);
        for (SEG.build(),i=q;i>=1;--i) modify(u[i],v[i],i);
        //for (i=1;i<=n;++i) printf("%d%c",SEG.query(dfn[i])," \n"[i==n]);
        for (i=1;i<n;++i)
        {
            int X=SEG.query(dfn[x[i]]),Y=SEG.query(dfn[y[i]]);
            int b=max(X,Y),w=min(X,Y)-1;
            if (1<=b&&b<=q) B[b].push_back(pi(x[i],y[i]));
            if (1<=w&&w<=q) W[w].push_back(pi(x[i],y[i]));
        }
        for (ret=0,i=1;i<=n;++i) fa[i]=u[i]=v[i]=i;
        for (i=1;i<=q;++i)
        {
            for (auto [x,y]:B[i]) merge(x,y);
            ans[i]=ret;
        }
        for (ret=0,i=1;i<=n;++i) fa[i]=u[i]=v[i]=i;
        for (i=q;i>=1;--i)
        {
            for (auto [x,y]:W[i]) merge(x,y);
            ans[i]=max(ans[i],ret);
        }
        for (i=1;i<=q;++i) printf("%d\n",ans[i]+1);
        for (idx=0,i=1;i<=n;++i) E[i].clear();
        for (i=1;i<=q;++i) B[i].clear(),W[i].clear(),ans[i]=0;
    }
}

J. Points on the Number Axis A

签到题,手玩一下就会发现答案为\(\frac{\sum_{i=1}^n x_i}{n}\)

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

const int MOD = 998244353;
int powe(int x, int p){
    int res=1;
    while (p>0){if (p&1) res=res*x%MOD; x=x*x%MOD; p>>=1;}
    return res;
}


signed main(){
    ios::sync_with_stdio(0); cin.tie(0);
    int n; cin >> n;
    int sum=0;
    for (int i=1; i<=n; ++i){
        int a; cin >> a;
        sum = (sum+a)%MOD;
    }
    cout << sum*powe(n, MOD-2)%MOD << '\n';
    return 0;
}

K. Points on the Number Axis B

刚开始读错J的题以为只能合并相邻两个,结果后面发现原来就是K题的意思,有点没绷住

徐神后面给了个神秘的生成函数做法,但由于数据范围估计跑不过以及机时问题就没写了,后面看了下题解好像挺傻逼的一个题

给题解中不清楚的地方补充一下,首先一个自然的想法就是设\(f_{n,m}\)表示当剩下\(n\)个数时,从左到右第\(m\)个数最后对答案的贡献系数

不难发现这个定义和题解中的类似,只需要变换下标就能相互转化,不过题解中的状态最后得到的式子相对比较简介,就以之为准

然后我们求出\(g_{i,j}\)的递推式后,可以直接大力猜测\(g_{i,j}=\prod(i-\frac{1}{2})(j-\frac{1}{2})\times C_{i+j}^i\),具体证明带进去化简一下就可以发现是成立的

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=2e6+5,mod=998244353,inv2=(mod+1)/2;
int n,ans,fact[N],ifac[N],prod[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
	RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
	for (ifac[n]=quick_pow(fact[n]),i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
	for (prod[0]=i=1;i<=n;++i) prod[i]=1LL*prod[i-1]*(i-inv2+mod)%mod;
}
inline int C(CI n,CI m)
{
	if (n<0||m<0||n<m) return 0;
	return 1LL*fact[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int main()
{
	RI i; for (scanf("%d",&n),init(2*n),i=1;i<=n;++i)
	{
		int x=i-1,y=n-i,p; scanf("%d",&p);
		(ans+=1LL*C(x+y,x)*prod[x]%mod*prod[y]%mod*ifac[x+y]%mod*p%mod)%=mod;
	}
	return printf("%d",ans),0;
}

L. LCMs

分类讨论题,刚开始漏了种Case卡了好久,后面过了其它几个题后回头来看才发现问题所在

首先特判一些Corner Case:

  • \(a=b\)时,输出\(0\)
  • \(a\mid b\)时,输出\(b\)
  • \(\gcd(a,b)>1\)时,输出\(a+b\)

否则仅考虑\(a,b\)互质的情况,不妨设\(a\)的最小质因子为\(p\)\(b\)的最小质因子为\(q\),同时结合样例我们知道\(2\)是一个很有用的中介点

直接在\(a,b,p,q,2\)这五个点间跑一个\(a\to b\)的最短路即可,当然具体实现时可以直接讨论走法,但还是很容易遗漏的说

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
int t,a,b;
signed main()
{
    for (scanf("%lld",&t);t;--t)
    {
        auto lcm=[&](CI x,CI y)
        {
            return x/__gcd(x,y)*y;
        };
        scanf("%lld%lld",&a,&b); int ans=lcm(a,b);
        if (a==b) ans=min(ans,0LL);
        if (b%a==0) ans=min(ans,b);
        if (__gcd(a,b)!=1) ans=min(ans,a+b);
        auto resolve=[&](CI n)
        {
            for (RI i=2;i*i<=n;++i) if (n%i==0) return i;
            return n;
        };
        int p=resolve(a),q=resolve(b);
        ans=min(ans,lcm(a,p)+lcm(p,b));
        ans=min(ans,lcm(a,q)+lcm(q,b));
        ans=min(ans,lcm(a,2)+lcm(2,b));
        ans=min(ans,lcm(a,p)+lcm(p,2)+lcm(2,b));
        ans=min(ans,lcm(a,2)+lcm(2,q)+lcm(q,b));
        ans=min(ans,lcm(a,p)+lcm(p,q)+lcm(q,b));
        ans=min(ans,lcm(a,p)+lcm(p,2)+lcm(2,q)+lcm(q,b));
        printf("%lld\n",ans);
    }
    return 0;
}

Postscript

这周继续出征西安,打完这场后这学期剩下的组队比赛就只有下个月中旬的省赛了

但6.1和6.2还有沟槽的蓝桥杯和百度之星,经典裸泳时间希望别被吊打的说

posted @ 2024-05-16 17:05  空気力学の詩  阅读(279)  评论(0编辑  收藏  举报