“华为智联杯”无线程序设计大赛暨2025年上海市大学生程序设计竞赛

Preface

摆烂一哥,又好久没训练了

在参加完本校夏令营后顺带就留在学校里了,这两天有空可能会多训练会

这场因为一段时间没训练了,导致前期题写的都不是很快,最后堪堪 9 题,从开场就开始写的 K 到最后也没能写完


A. 序列

由于子序列和顺序无关,因此考虑构造序列 \(c_1,c_2,\dots,c_k\),表示一共用了 \(k\) 种颜色,颜色 \(i\) 的数量是 \(c_i\)

\(m=\sum_{i=1}^k c_i\),则序列的价值可以表示为:

\[\sum_{i=1}^k \left(2^{c_i}-1\right)\times 2^{m-c_i} \]

注意到当固定 \(m\) 时,这个式子的值范围是 \([2^m-1,m2^{m-1}]\) 的,因此只需要枚举 \(m\le 60\) 的值即可

此时直接钦定 \(c_i\) 单调不降,直接爆搜即可通过,复杂度是和五边形数相关的

#include<cstdio>
#include<iostream>
#include<vector>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
int x,m,pw2[65]; bool flag;
inline void DFS(vector <int>& c,CI left,CI lst,CI sum)
{
    if (sum==x&&left==0)
    {
        flag=1; puts("Yes");
        printf("%d\n",m);
        for (RI i=0;i<(int)c.size();++i)
        for (RI j=1;j<=c[i];++j) printf("%d ",i+1);
        return;
    }
    for (RI i=lst;i<=left;++i)
    {
        __int128 val=__int128((1LL<<i)-1)*__int128((1LL<<m-i));
        // printf("%lld %lld\n",i,val);
        if (val+sum>x) continue;
        c.push_back(i);
        DFS(c,left-i,i,sum+val);
        if (flag) return;
        c.pop_back();
    }
}
signed main()
{
    scanf("%lld",&x);
    for (m=1;m<=60;++m)
    {
        vector <int> c;
        DFS(c,m,1,0);
        if (flag) break;
    }
    if (!flag) puts("No");
    return 0;
}

C. 饺子

感觉发病了写了三分套二分,好在最后卡常硬是卡过去了

先不考虑 \([l,r]\) 的限制,令函数 \(f(x)\) 表示恰好吃了 \(x\) 个饺子能获得的最大收益,显然 \(f(x)\) 是个单峰函数

求出 \(f\) 的极值点 \(k\) 后会发现其实可能的取值只有 \(\{0,m,k,l,r\}\) 这五个,取一个最大的即可

至于如何计算 \(f(x)\),可以考虑二分吃掉的饺子中的最小权值,简单计算一下即可

事实上求极值点 \(k\) 可以直接贪心,复杂度降为一个 \(\log\)

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

const int N = 1e5+5;
const int INF = (int)1e15+5;

int n, m, val, l, r;
int s[N], a[N], b[N], c[N];

array<int, 3> calc2(int i, int x) {
    int score=0, num=0, eqnum = 0;
    if (c[i]+a[i] >= x)
    {
        num++, score+=(c[i]+a[i]);
        if (c[i]+a[i]==x) ++eqnum;
    }
    int num2 = (a[i]-x)/b[i];
    num2 = max(0LL, min(s[i]-1, num2));
    if (num2==0) return {score,num,eqnum};
    if (a[i]-b[i]*num2 == x) ++eqnum;
    num += num2;
    score += (a[i]-b[i] + a[i]-b[i]*num2) * num2 /2;
    return {score, num, eqnum}; 
} 

int calc(int num) {
    if (num == 0) return 0;
    int L = -2e12, R = 2e6;
    while (L <= R) {
        int M = (R + L) /2;
        // printf("%lld %lld %lld\n",L,R,M);
        int xnum = 0, score = 0, eqnum = 0;
        for (int i=1; i<=n; ++i) {
            auto [aa, bb, cc] = calc2(i, M);
            score += aa, xnum += bb, eqnum += cc;
            if (xnum > num && xnum - eqnum >= num) break;
        }
        if (xnum - eqnum < num && num <= xnum) return score-(xnum-num)*M;
        if (xnum >= num) L = M+1; else R = M-1;
    }
    assert(0);
}

int solve() {
    cin >> n >> m >> val >> l >> r;
    int ssum = 0;
    for (int i=1; i<=n; ++i) {
        cin >> s[i] >> a[i] >> b[i] >> c[i];
        ssum += s[i];
    }

    int L=0, R=min(m,ssum);
    while (R - L > 2) {
        int M1 = L + (R - L)/3;
        int M2 = R - (R - L)/3;
        int val1 = calc(M1);
        int val2 = calc(M2);
        if (val1>=val2) R = M2; else L = M1;
    }
    int pos = L;
    for (int i=L + 1; i<=R; ++i) {
        int val = calc(i);
        if (val>calc(pos)) pos = i;
    }

    // printf("calc(l) = %lld\n",calc(l));

    int ans = max(calc(0),calc(min(m,ssum)));
    if (ssum<l) return max(ans,calc(pos));
    r=min(r,ssum);
    ans = max(ans,max(calc(l),calc(r))+val);
    if (l<=pos&&pos<=r) ans = max(ans,calc(pos)+val);
    else ans = max(ans,calc(pos));
    return ans;
}


signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T; while (T--) cout << solve() << '\n';
    return 0;
}

D. 与或博弈

不难发现先手必须一步或零步完成要求,简单讨论下即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t; long long a,b,x,y;
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%lld%lld%lld%lld",&a,&b,&x,&y);
        if (a==x&&b==y) { puts("Yes"); continue; }
        if (a!=x&&b!=y) { puts("No"); continue; }
        if (a==x) puts((b|y)==y?"Yes":"No");
        else puts((a&x)==x?"Yes":"No");
    }
    return 0;
}

E. Djangle 的数据结构

刚开始写了个很 trivial 的东西,query 时只在区间内数全相同时进行在操作,然后喜提 TLE

后面想到个关键优化,不妨维护区间 LCM,如果 \(x\) 是这个值的倍数就无需操作,势能分析一波会发现复杂度很对

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
typedef long long LL;
const int N=400005,INF=1<<30;
int t,n,q,a[N];
class Segment_Tree
{
    private:
        int tag[N],G[N],L[N]; LL sum[N];
        #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 apply(CI now,CI mv,CI len)
        {
            tag[now]=G[now]=L[now]=mv; sum[now]=1LL*mv*len;
        }
        inline void pushdown(CI now,CI ls,CI rs)
        {
            if (tag[now]) apply(now<<1,tag[now],ls),apply(now<<1|1,tag[now],rs),tag[now]=0;
        }
        inline void pushup(CI now)
        {
            sum[now]=sum[now<<1]+sum[now<<1|1];
            G[now]=__gcd(G[now<<1],G[now<<1|1]);
            if (L[now<<1]>INF||L[now<<1|1]>INF) L[now]=INF+1; else
            {
                LL val=1LL*L[now<<1]*L[now<<1|1]/__gcd(L[now<<1],L[now<<1|1]);
                if (val>INF) L[now]=INF+1; else L[now]=val;
            }
        }
    public:
        inline void build(TN)
        {
            tag[now]=0; if (l==r) return (void)(G[now]=L[now]=sum[now]=a[l]);
            int mid=l+r>>1; build(LS); build(RS); pushup(now);
        }
        inline void modify(CI beg,CI end,CI mv,TN)
        {
            if (beg<=l&&r<=end) return apply(now,mv,r-l+1); int mid=l+r>>1; pushdown(now,mid-l+1,r-mid);
            if (beg<=mid) modify(beg,end,mv,LS); if (end>mid) modify(beg,end,mv,RS); pushup(now);
        }
        inline LL query(CI beg,CI end,CI x,TN)
        {
            if (beg<=l&&r<=end)
            {
                if (x%L[now]==0) return sum[now];
                if (G[now]==L[now])
                {
                    apply(now,__gcd(G[now],x),r-l+1);
                    return sum[now];
                }
            }
            int mid=l+r>>1; LL ans=0; pushdown(now,mid-l+1,r-mid);
            if (beg<=mid) ans+=query(beg,end,x,LS); if (end>mid) ans+=query(beg,end,x,RS);
            pushup(now); return ans;
        }
        #undef TN
        #undef LS
        #undef RS
}SEG;
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d%d",&n,&q);
        for (RI i=1;i<=n;++i) scanf("%d",&a[i]);
        for(SEG.build();q;--q)
        {
            int opt,l,r,x;
            scanf("%d%d%d%d",&opt,&l,&r,&x);
            if (opt==0) SEG.modify(l,r,x);
            else printf("%lld\n",SEG.query(l,r,x));
        }
    }
    return 0;
}

G. 矩阵

考虑求出第一个大于 \(n\) 的质数 \(P\),经过暴力检验发现 \(P\le n+40\),然后按照如下规则构造即可

\[\begin{matrix} 1&2&3&\cdots&n\\ 1+P&2+P&3+P&\cdots&n+P\\ 1+2P&2+2P&3+2P&\cdots&n+2P\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ 1+(n-1)P&2+(n-1)P&3+(n-1)P&\cdots&n+(n-1)P\\ \end{matrix} \]

#include<cstdio>
#include<iostream>
#include<assert.h>
#define RI register int
#define CI const int&
using namespace std;
const int N=2505;
int n;
inline bool is_prime(CI x)
{
    if (x==1) return 0;
    for (RI i=2;i*i<=x;++i)
    if (x%i==0) return 0;
    return 1;
}
int main()
{
    /*for (RI i=1;i<=2500;++i)
    {
        int P;
        for (RI j=i+1;;++j)
        if (is_prime(j)) { P=j; break; }
        assert(P-i<=40);
    }*/
    scanf("%d",&n);
    int P;
    for (RI i=n+1;;++i)
    if (is_prime(i)) { P=i; break; }
    for (RI i=1;i<=n;++i)
    for (RI j=1;j<=n;++j)
    printf("%d%c",1+(i-1)*P+j-1," \n"[j==n]);
    return 0;
}

H. V 我 112.5

签到

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int x;
int main()
{
    scanf("%dd",&x);
    return printf("Vivo %.3lf",50.0*(1.0+1.0*x/100.0)),0;
}

I. 真相

简单 DP,令 \(f_{i,j}\) 表示点 \(i\) 的子树里有 \(j\) 个人说真话

每次转移先用树上背包合并儿子的贡献,然后讨论下当前点的两种情况即可

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

const int N = 5005;
const int MOD = 998244353;

void inc(int &x, int a) {if ((x+=a)>=MOD) x-=MOD;}

int n, A[N], sz[N];
vector<int> G[N];
int f[N][N], g[N][N];


void dfs(int x, int fa) {
    static int tmp[N];
    g[x][0] = 1;
    sz[x] = 0;
    for (int v : G[x]) {
        if (v == fa) continue;
        dfs(v, x);
        for (int i=sz[x]; i>=0; --i) {
            for (int j=0; j<=sz[v]; ++j) {
                inc(tmp[i+j], g[x][i]*f[v][j]%MOD);
            }
        }
        for (int j=0; j<=sz[x]+sz[v]; ++j) g[x][j] = tmp[j], tmp[j] = 0;
        sz[x] += sz[v];
    }
    sz[x] += 1;
    

    int sumg = 0;
    // for (int i=0; i<=sz[x]; ++i) printf("g[%d][%d]=%d\n", x, i, g[x][i]), inc(sumg, g[x][i]);
    // printf("sumg=%lld\n", sumg);
    for (int i=0; i<=sz[x]; ++i) {
        f[x][i] = 0;
        if (A[x]==i) {
            if (i>0) inc(f[x][i], g[x][i-1]);
        } else inc(f[x][i], g[x][i]);
        // printf("f[%lld][%lld]=%lld\n", x, i, f[x][i]);
    }
}

int solve() {
    cin >> n;
    for (int i=1; i<=n; ++i) cin >> A[i];
    for (int i=1; i<n; ++i) {
        int u, v; cin >> u >> v;
        G[u].push_back(v); G[v].push_back(u);
    }
    dfs(1, -1);
    // printf("sz:"); for (int i=1; i<=n; ++i) printf("%d ", sz[i]); printf("\n");
    int ans = 0;
    for (int i=0; i<=n; ++i) inc(ans, f[1][i]);

    for (int i=1; i<=n; ++i) G[i].clear();
    for (int i=1; i<=n; ++i) for (int j=0; j<=sz[i]; ++j) g[i][j] = 0;
    return ans;
}


signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T; while (T--) cout << solve() << '\n';
    return 0;
}

J. 画圈

先求出只使用黑边时的连通块,然后枚举每条白边,若该边两个端点在同一连通块中显然可以直接产生 \(1\) 的贡献

否则把连通块缩成一个点后,建立一棵白边的生成树,那么所有剩下的非树边都可以产生 \(1\) 的贡献

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
int t,n,m,fa[N];
inline int getfa(CI x)
{
    return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d%d",&n,&m);
        vector <pi> E[2]; // col=0 -> white; col=1 -> black
        for (RI i=1;i<=m;++i)
        {
            int col,x,y;
            scanf("%d%d%d",&x,&y,&col);
            E[col].push_back({x,y});
        }
        for (RI i=1;i<=n;++i) fa[i]=i;
        for (auto [x,y]:E[1]) fa[getfa(x)]=getfa(y);
        int ans=0; vector <pi> EE;
        for (auto [x,y]:E[0])
        {
            x=getfa(x); y=getfa(y);
            if (x==y) ++ans; else EE.push_back({x,y});
        }
        ans+=(int)EE.size();
        for (auto [x,y]:EE)
        {
            x=getfa(x); y=getfa(y);
            if (x!=y) --ans,fa[x]=y;
        }
        printf("%d\n",ans);
    }
    return 0;
}

K. 神之一手

这题扔给徐神写了,但超过 \(80\) 的情况一直过不了样例,十分神秘


M. 魔法使考核

不难发现翻倍操作可以转化为全局,因此可以枚举最大能用的翻倍操作数量

考虑单个人时,若当前数是奇数,则必然要用一次加一得来;否则如果还有翻倍操作先用掉一定是最优的

注意爆 long long 的问题

#include <bits/stdc++.h>

using i128 = __int128_t;
using llsi = long long signed int;

llsi calc(llsi x, llsi res) {
    llsi cur=0;
    while (x>0)
    {
        if (x&1) ++cur,x^=1;
        if (res>0) --res,x>>=1; else return cur+x;
    }
    return cur;
}

void write(i128 x) {
    if(x == 0) return std::cout << "0", void(0);

    int stack[128], top = 0;
    while(x) stack[top++] = x % 10, x /= 10;
    while(top) std::cout << char(stack[--top] + '0');
    return ;
}

int main() {
    llsi n, x, y;
    std::cin >> n >> x >> y;
    i128 ans = 0x7fff'ffff'ffff'ffff;
    ans *= 998244353;
    std::vector<llsi> a(n);
    for(auto &a: a) std::cin >> a;

    for(llsi p = 0; p <= 30; ++p) {
        i128 sum = 0;
        
        for(auto a: a) sum += calc(a, p);
        ans = std::min(ans, i128(x) * sum + i128(y) * p);
    }

    write(ans); std::cout << char(10);
    return 0;
}

Postscript

下周可能会打打多校,但博客不一定有空写

posted @ 2025-07-13 16:50  空気力学の詩  阅读(206)  评论(0)    收藏  举报