第十一届中国大学生程序设计竞赛 哈尔滨站(CCPC 2025 Harbin Site)

Preface

上周的 CCPC 哈尔滨,同时间进行的 ICPC 南京又混了个金尾,只能说狗运这一块

哈尔滨的题感觉和去年郑州比较像,基本上大家都是 7~8 个题然后拼手速,只可惜这次被 Adhoc 题 I 防了 4h 最后中罚时 7 题喜提银首

这么看来去年郑州的高位金完全是运气爆棚啊,CCPC 拿金确实还是太困难了


A. k-子集和最大公约数问题

要点脑子的签到,第一个答案就是所有元素差值的 \(\gcd\),第二个答案要除以初始时所有数的 \(\gcd\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N];
signed main()
{
    for (scanf("%lld",&t);t;--t)
    {
        scanf("%lld",&n);
        int G=0,D=0;
        for (RI i=1;i<=n;++i)
        {
            scanf("%lld",&a[i]);
            G=__gcd(G,a[i]);
            if (i>1) D=__gcd(D,abs(a[i]-a[i-1]));
        }
        if (D==0) { puts("infinite"); continue; }
        printf("%lld %lld\n",D,D/G);
    }
    return 0;
}

B. 液压机

模拟,不难发现 \(y\) 坐标是没有任何影响的,可以忽略

\(x\) 方向上的运动过程可以分讨,先特判掉两个墙都不动的情况

否则由于两面墙之间的距离会以最少 \(\frac{1}{20}\) 的比例幂次增长,小球碰撞的次数是 \(\log\) 级别的,可以暴力模拟之

#include<cstdio>
#include<iostream>
#include<cmath>
#define RI register int
#define CI const int&
typedef long double LDB;
const LDB EPS=1e-9;
int cases; LDB x,y,vx,vy,Y1,y2,vy1,vy2,x1,x2,vx1,vx2;
inline int sgn(const LDB& x)
{
    return std::fabs(x)<EPS?0:(x<0?-1:1);
}
int main()
{
    for (scanf("%d",&cases);cases;--cases)
    {
        std::scanf("%Lf%Lf%Lf%Lf",&x,&y,&vx,&vy);
        std::scanf("%Lf%Lf%Lf%Lf",&Y1,&y2,&vy1,&vy2);
        std::scanf("%Lf%Lf%Lf%Lf",&x1,&x2,&vx1,&vx2);
        LDB T=(y2-Y1)/(vy1+vy2),X,Y=Y1+T*vy1;
        if (sgn(vx1)==0&&sgn(vx2)==0)
        {
            LDB dt=std::fabs(x1-x2)/std::fabs(vx);
            // printf("T = %.12Lf, dt = %.12Lf\n",T,dt);
            T=T-std::floor(T/(2.0L*dt)+EPS)*(2.0L*dt);
            while (sgn(T)>0)
            {
                LDB t,xt;
                if (sgn(vx)>0) xt=x2; else xt=x1;
                t=std::min(std::fabs(xt-x)/std::fabs(vx),T);
                x+=vx*t; T-=t;
                if (sgn(T)>0) vx=-vx;
            }
            X=x;
        } else
        {
            while (sgn(T)>0)
            {
                LDB t,xt,vt;
                if (sgn(vx)>0) xt=x2,vt=vx2; else xt=x1,vt=vx1;
                if (sgn(std::fabs(vx)-vt)<=0) t=T;
                else t=std::min(std::fabs(xt-x)/(std::fabs(vx)-vt),T);
                x+=vx*t; x1-=vx1*t; x2+=vx2*t; T-=t;
                if (sgn(T)>0) vx=-vx;
            }
            X=x;
        }
        printf("%.12Lf %.12Lf\n",X,Y);
    }
    return 0;
}

G. 扫雪

考虑从下往上,从右往左扫描整个矩阵

对于第 \(i\) 列,用 \(bins_i\) 记录下这一列还可以容纳多少雪

如果当前遇到的 \(a_{i,j}\) 是负的,就直接放进 \(bins_j\) 中;否则考虑将其消除

对于 \(k\in [j,m]\),依次扫描每个 \(bins_k\),尽量把雪移动过去即可

实现时只需要用一个栈维护所有还能容纳雪的 \(bins\) 位置即可,注意如果一个正的 \(a_{i,j}\) 消除不完就应该直接丢弃

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int t,n,m,a[N][N],bins[N];
signed main()
{
    for (scanf("%lld",&t);t;--t)
    {
        scanf("%lld%lld",&n,&m);
        long long sum=0;
        for (RI i=1;i<=n;++i)
        for (RI j=1;j<=m;++j)
        scanf("%lld",&a[i][j]),sum+=abs(a[i][j]);
        for (RI j=1;j<=m;++j) bins[j]=0;
        for (RI i=n;i>=1;--i)
        {
            static int stk[N]; int top=0;
            for (RI j=m;j>=1;--j)
            {
                if (a[i][j]<0) bins[j]+=a[i][j];
                if (bins[j]<0) stk[++top]=j;
                while (top>0&&a[i][j]>0)
                {
                    int dlt=min(a[i][j],-bins[stk[top]]);
                    sum-=2LL*dlt;
                    a[i][j]-=dlt; bins[stk[top]]+=dlt;
                    if (bins[stk[top]]==0) --top;
                }
            }
        }
        printf("%lld\n",sum);
    }
    return 0;
}

I. 六边形翻转

关了我们队三人几乎一整场,太有人类智慧了

首先这题等价于将两个图的颜色异或,然后判断得到的图能否全部删成白色

把样例画出来会发现一个很牛的性质:三个方向上的每条线上,黑色块的数量都是偶数

后面尝试思考了下发现这个性质很对,因为一次操作并不会改变上面这个东西,然后猜了一发交上去就过了

#include<cstdio>
#include<iostream>
#include<map>
#define RI register int
#define CI const int&
using namespace std;
int t,n,m;
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d%d",&n,&m);
        map <int,int> X,Y,Z;
        for (RI i=1;i<=n+m;++i)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            X[x]^=1; Y[y]^=1; Z[z]^=1;
        }
        bool flag=1;
        for (auto [_,val]:X) if (val!=0) flag=0;
        for (auto [_,val]:Y) if (val!=0) flag=0;
        for (auto [_,val]:Z) if (val!=0) flag=0;
        puts(flag?"YES":"NO");
    }
    return 0;
}

J. 幻想乡的裁判长

徐神 solo 的字符串,好像是个 Manacher 分讨

#include <bits/stdc++.h>

constexpr int $n = 20'000'007;

namespace manacher {
    static int R[$n];
    template<typename F>
    void odd(char *s, int n, F cb) {
        s[0] = 1, s[n + 1] = 0;
        for(int i = 1, m = 0, r = 0, k, l; i <= n; ++i) {
            if(i <= r) {
                k = m * 2 - i;
                if(i + R[k] <= r) {
                    R[i] = R[k];
                    cb(i - R[i] + 1, i + R[i] - 1);
                    continue;
                }
            } else r = i;
            m = i, l = m * 2 - r;
            while(s[l] == s[r]) --l, ++r;
            R[i] = r - i; cb(l + 1, --r);
        }
    }

    template<typename F>
    void even(char *s, int n, F cb) {
        s[0] = 1, s[n + 1] = 0;
        for(int i = 1, m = 0, r = 0, k, l; i <= n; ++i) {
            if(i <= r) {
                k = m * 2 - i;
                if(i + R[k] <= r) {
                    R[i] = R[k];
                    if(R[i]) cb(i - R[i], i + R[i] - 1);
                    continue;
                }
            } else r = i;
            m = i, l = m * 2 - r - 1;
            while(s[l] == s[r]) --l, ++r;
            R[i] = r - i; r -= 1; l += 1;
            if(r > l) cb(l, r);
        }
    }
} // namespace manacher

int n, ans, ans_l, ans_r;
char org_s[$n], s[$n];
int map[$n], prevNotW[$n], nextNotW[$n];

inline bool isLeftBound(int pos) { return map[pos - 1] == map[pos] - 1; }
inline bool isRightBound(int pos) { return map[pos + 1] == map[pos] + 1; }

void update_ans(int l, int r) {
    if(l > r) return ;
    if(r - l + 1 > ans) {
        ans = r - l + 1;
        ans_l = l;
        ans_r = r;
    }
}

void callback(int l, int r) {
    // std::cerr << "callback(" << l << ", " << r << ")\n";
    int org_l = map[l], org_r = map[r];
    
    // if(l == 5 && r == 8) {
    //     std::cerr << "org_l, org_r = " << org_l << ", " << org_r << char(10);
    //     std::cerr << isLeftBound(l) << ", " << isRightBound(r) << char(10);
    // }
    
    assert(isLeftBound(l) || isRightBound(r));
    if(isLeftBound(l) && isRightBound(r)) return update_ans(org_l, org_r);
    if( !isLeftBound(l) && org_s[org_r] == 'v') return update_ans(org_l + 1, org_r - 1);
    if(!isRightBound(r) && org_s[org_l] == 'v') return update_ans(org_l + 1, org_r - 1);

    int skip = std::min(nextNotW[org_l] - org_l, org_r - prevNotW[org_r]);
    if( isLeftBound(l) && org_s[org_r - skip] == 'v'                              ) return update_ans(org_l + skip, org_r - skip - 1);
    if( isLeftBound(l) && org_s[org_r - skip] == 'w' && org_s[org_l + skip] == 'v') return update_ans(org_l + skip + 1, org_r - skip - 1);
    if(isRightBound(r) && org_s[org_l + skip] == 'v'                              ) return update_ans(org_l + skip + 1, org_r - skip);
    if(isRightBound(r) && org_s[org_l + skip] == 'w' && org_s[org_r - skip] == 'v') return update_ans(org_l + skip + 1, org_r - skip - 1);
}

void work() {
    scanf("%d", &n);
    scanf("%s", org_s + 1);

    int p = 1;
    for(int i = 1; i <= n; ++i) {
        if(org_s[i] == 'w') {
            s[p] = s[p + 1] = 'v';
            map[p] = map[p + 1] = i;
            p += 2;
        } else {
            s[p] = org_s[i];
            map[p] = i;
            p += 1;
        }
    }
    map[0] = 0; map[p] = n + 1;
    // std::cerr << "s.len = " << p - 1 << char(10);
    
    prevNotW[0] = 0;
    for(int i = 1; i <= n; ++i) {
        if(org_s[i] == 'w') prevNotW[i] = prevNotW[i - 1];
        else                prevNotW[i] = i;
    }
    nextNotW[n + 1] = n + 1;
    for(int i = n; i >= 1; --i) {
        if(org_s[i] == 'w') nextNotW[i] = nextNotW[i + 1];
        else                nextNotW[i] = i;
    }

    ans = 0;
    manacher::odd(s, p - 1, callback);
    manacher::even(s, p - 1, callback);

    // std::cerr << "ans_l, ans_r = " << ans_l << ", " << ans_r << char(10);
    for(int i = ans_l; i <= ans_r; ++i) putchar(org_s[i]);
    putchar(10);

    return ;
}

int main() {
    int T; scanf("%d", &T);
    while(T--) work();
    return 0;
}


K. 01 背包

又是神秘 Guess 题,但这个比较对我胃口一下就猜出来了

由于题面要求对任意 \(W\) 都要满足,因此直接大力猜测一手物品数量 \(n=W\),并且体积满足 \(w_i=i\)

现在难点就是要分配价值,根据样例先假设 \(w_1=x,w_2=x+1\)

接下来每加入一个物品 \(i\),我们都要让最优解选择物品 \(i\) 而贪心不选择物品 \(i\)

简单推一推会有 \(w_3=2x+2,w_4=3x+3,\dots,w_i=(i-1)\times (x+1)\)

为了卡贪心需要让 \(w_1\) 的单价严格最高,因此有 \(x>(n-1)\times (x+1)\),解得 \(x_{\min}=n\),代入式子输出即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int main()
{
    int Wm; scanf("%d",&Wm);
    int n=Wm; printf("%d\n",n);
    for (RI i=1;i<=n;++i)
    printf("%d%c",i," \n"[i==n]);
    int x=Wm;
    for (RI i=1;i<=n;++i)
    {
        int val=(i==1?x:(i-1)*(x+1));
        printf("%d%c",val," \n"[i==n]);
    }
    return 0;
}


L. 网格避障

签到,直接枚举所有状态做一个按列 DP 即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=105,INF=1e9;
int t,n,m,k,r[N],c[N],valid[N][N],f[N][N];
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d%d%d",&n,&m,&k);
        for (RI i=0;i<k;++i)
        scanf("%d%d",&r[i],&c[i]);
        for (RI mask=0;mask<(1<<k);++mask)
        {
            for (RI i=1;i<=n;++i)
            for (RI j=1;j<=m;++j)
            {
                valid[i][j]=1;
                f[i][j]=INF;
            }
            for (RI i=0;i<k;++i)
            if ((mask>>i)&1)
            {
                for (RI j=1;j<=r[i];++j)
                valid[j][c[i]]=0;
            } else
            {
                for (RI j=r[i];j<=n;++j)
                valid[j][c[i]]=0;
            }
            for (RI i=1;i<=n;++i) f[i][1]=0;
            for (RI j=1;j<=m;++j)
            {
                for (RI i=1;i<=n;++i)
                if (valid[i][j]) f[i][j]=min(f[i][j],f[i][j-1]+1);
                for (RI i=2;i<=n;++i)
                if (valid[i][j]) f[i][j]=min(f[i][j],f[i-1][j]+1);
                for (RI i=n-1;i>=1;--i)
                if (valid[i][j]) f[i][j]=min(f[i][j],f[i+1][j]+1);
            }
            int ans=INF;
            for (RI i=1;i<=n;++i) ans=min(ans,f[i][m]);
            printf("%d%c",ans==INF?-1:ans," \n"[mask==(1<<k)-1]);
        }
    }
    return 0;
}

M. 连通的正三角形

比赛最后时刻疯狂 Rush,结果没想到容斥

首先可以把问题转化为统计三元组 \((i,j,k)\) 的个数,其中:

  • \(a_i=b_j=c_k\)
  • \(i+j\ge n,i+k\ge n,j+k\ge n\)
  • \(i+j+k\ne 2n\)\(<2n\) 是正着的三角形,\(>2n\) 的是反着的,\(=2n\) 会退化)

第一条直接枚举钦定当前算 \(0\) 还是算 \(1\) 的,第二条可以枚举 \(i\) 然后用后缀和快速算 \(j,k\) 的贡献

仔细分析我们会发现一个牛逼的性质,那就是 \(i+j+k=2n\) 天然满足第二条性质,因此我们只要用之前的数量减去违反第三天的数量即可

具体实现时可以 bitset 也可以 FFT,形式已经非常裸了

#include <bits/stdc++.h>

using llsi = long long;

const int N=8e5+5;
namespace Poly {
    using LDB = long double;
    const LDB PI=acosl(-1);
    struct Complex
    {
        LDB x,y;
        inline Complex(LDB x=0, LDB y=0): x(x), y(y) {}
        inline Complex conj(void)
        {
            return Complex(x,-y);
        }
        friend inline Complex operator + (const Complex& A,const Complex& B)
        {
            return Complex(A.x+B.x,A.y+B.y);
        }
        friend inline Complex operator - (const Complex& A,const Complex& B)
        {
            return Complex(A.x-B.x,A.y-B.y);
        }
        friend inline Complex operator * (const Complex& A,const Complex& B)
        {
            return Complex(A.x*B.x-A.y*B.y,A.x*B.y+A.y*B.x);
        }
    }; int lim,p,rev[N];
    inline void init(int n)
    {
        for (lim=1,p=0;lim<=n;lim<<=1,++p);
        for (int i=0;i<lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<p-1);
    }
    inline void FFT(Complex *f,int opt)
    {
        for (int i=0;i<lim;++i) if (i<rev[i]) std::swap(f[i],f[rev[i]]);
        for (int i=1;i<lim;i<<=1)
        {
            Complex D(cosl(PI/i),opt*sinl(PI/i));
            for (int j=0;j<lim;j+=(i<<1))
            {
                Complex W(1,0);
                for (int k=0;k<i;++k,W=W*D)
                {
                    Complex x=f[j+k],y=W*f[i+j+k];
                    f[j+k]=x+y; f[i+j+k]=x-y;
                }
            }
        }
        if (opt==-1)
        {
            for (int i=0;i<lim;++i) f[i].x/=lim,f[i].y/=lim;
        }
    }
}
using namespace Poly;
int n;
std::string s1, s2, s3;

llsi work() {
    
    static Complex A[N],C[N];

    init(2*n);
    for (int i=0;i<lim;++i) A[i]=C[i]=Complex();
    for (int i=1;i<=n;++i)
    {
        if (s1[i]=='0') A[i].x+=1.0;
        if (s3[i]=='0') C[i].x+=1.0;
    }
    FFT(A,1); FFT(C,1);
    for (int i=0;i<lim;++i) A[i]=A[i]*C[i];
    FFT(A,-1);
    
    static llsi sfx_a[N],sfx_c[N];

    for (int i=n;i>=1;--i)
    {
        sfx_a[i]=sfx_a[i+1]+(s1[i]=='0');
        sfx_c[i]=sfx_c[i+1]+(s3[i]=='0');
    }

    llsi res=0,sum=(s1[n]=='0')*(s3[n]=='0');

    for (int i=1;i<=n;++i)
    {
        if (i!=n)
        {
            sum+=(s1[n-i]=='0')*sfx_c[std::max(n-i,i)]+(s3[n-i]=='0')*sfx_a[std::max(n-i,i)];
            if (i<=n-i) sum-=(s1[n-i]=='0')*(s3[n-i]=='0');
        }
        if (s2[i]=='0') res+=sum;
    }

    for (int i=1;i<=n;++i)
    if (s2[i]=='0') res-=(llsi)(A[2*n-i].x+0.5L);

    return res;
}

int main() {
    std::ios::sync_with_stdio(false);
    int T; std::cin >> T;
    while(T--) {
        std::cin >> n;
        std::cin >> s1 >> s2 >> s3;
        s1=" "+s1; s2=" "+s2; s3=" "+s3;
        llsi ans = work();
        // std::cout<<ans<<'\n';
        for(auto &c: s1) c = (c ^ 1);
        for(auto &c: s2) c = (c ^ 1);
        for(auto &c: s3) c = (c ^ 1);
        ans += work();
        std::cout << ans << char(10);
    }
    return 0;
}

Postscript

这周末由于要出去上课,队友要去济南旅游,导致没法 VP

下周 ICPC 上海之前可能只有下周三能 VP 一场 ICPC 沈阳 / CCPC 济南了

你问我 ICPC 南京的游记在哪,我只能说有生之年系列了

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