第十屆中國大學生程式設計競賽 重慶站(CCPC 2024 Chongqing Site)

空気力学の詩發表於2024-11-29

Preface

事實證明比賽前真得訓練一下,不然正賽的時候瘋狂發病真頂不住

本來想週四晚上 VP 下杭州或者上海,但好像都沒上 QOJ,遂找了這賽季的 CCPC 重慶聊以慰藉

本來以為計數場要寄了的,結果最後徐神絕殺 4:59 絕殺 D 之後也是來到 9 題區,感覺也沒這麼毒瘤


A. 乘積,尤拉函式,求和

很典的一個題,和暑假牛客多校的一個題幾乎一樣

由於尤拉函式 \(\phi(n)=n\times \prod \frac{p_i-1}{p_i}\),因此一個集合的貢獻可以分成兩部分,一個是直接乘積起來的部分,一個是後面對每個質因子只求一次貢獻的部分

考慮令 \(f(i,mask)\) 表示處理了前 \(i\) 個數,此時出現過的質因數集合的狀壓值為 \(mask\) 時,前面部分的貢獻是多少,轉移就是個揹包

直接做複雜度肯定不能接受,根據經典套路 \(\sqrt {3000}\) 以內的質數只有 \(16\) 個,因此我們可以只狀壓較小的質因子

之後將所有數按照最大質因子分組,顯然同一組內的貢獻可以一起討論,總複雜度 \(O(n\times 2^{16})\)

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=3005,all=1<<16,mod=998244353;
int n,a[N],vis[N],pri[N],id[N],cnt,f[2][1<<16];
inline void inc(int& x,CI y)
{
    if ((x+=y)>=mod) x-=mod;
}
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;
}
struct ifo
{
    int val,S;
    inline ifo(CI VAL=0,CI NS=0)
    {
        val=VAL; S=NS;
    }
}; vector <ifo> vec[N];
inline void init(CI n)
{
    for (RI i=2;i<=n;++i)
    {
        if (!vis[i]) id[i]=cnt,pri[cnt++]=i;
        for (RI j=0;j<cnt&&i*pri[j]<=n;++j)
        if (vis[i*pri[j]]=1,i%pri[j]==0) break;
    }
}
int main()
{
    init(3000); scanf("%d",&n);
    for (RI i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);
        ifo cur(a[i],0);
        for (RI j=0;j<16;++j)
        if (a[i]%pri[j]==0)
        {
            cur.S|=(1<<j);
            while (a[i]%pri[j]==0) a[i]/=pri[j];
        }
        if (a[i]>1) vec[id[a[i]]].push_back(cur);
        else vec[0].push_back(cur);
    }
    f[0][0]=1;
    for (auto [val,S]:vec[0])
    {
        for (RI mask=0;mask<all;++mask) f[1][mask]=f[0][mask];
        for (RI mask=0;mask<all;++mask) inc(f[1][mask|S],1LL*f[0][mask]*val%mod);
        for (RI mask=0;mask<all;++mask) f[0][mask]=f[1][mask];
    }
    for (RI mask=0;mask<all;++mask) f[1][mask]=0;
    for (RI i=16;i<cnt;++i)
    {
        if (vec[i].empty()) continue;
        for (auto [val,S]:vec[i])
        {
            static int g[2][1<<16];
            for (RI mask=0;mask<all;++mask) g[0][mask]=f[0][mask],g[1][mask]=f[1][mask];
            for (RI mask=0;mask<all;++mask)
            {
                inc(g[1][mask|S],1LL*f[1][mask]*val%mod);
                inc(g[1][mask|S],1LL*f[0][mask]*(val*(pri[i]-1)/pri[i])%mod);
            }
            for (RI mask=0;mask<all;++mask) f[0][mask]=g[0][mask],f[1][mask]=g[1][mask];
        }
        for (RI mask=0;mask<all;++mask) inc(f[0][mask],f[1][mask]),f[1][mask]=0;
    }
    int ans=0;
    for (RI mask=0;mask<all;++mask)
    {
        int cur=f[0][mask];
        for (RI i=0;i<16;++i)
        if ((mask>>i)&1) cur=1LL*cur*(pri[i]-1)%mod*quick_pow(pri[i])%mod;
        inc(ans,cur);
    }
    return printf("%d",ans),0;
}

B. osu!mania

簽到,注意對浮點數的處理儘可能不要丟精度

#include<cstdio>
#include<iostream>
#include<cmath>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
int t,ppmax,a,b,c,d,e,f;
signed main()
{
    for (scanf("%lld",&t);t;--t)
    {
        scanf("%lld%lld%lld%lld%lld%lld%lld",&ppmax,&a,&b,&c,&d,&e,&f);
        int Acc=round(1.0L*(10000*(300*a+300*b+200*c+100*d+50*e))/(300*(a+b+c+d+e+f)));
        int A=Acc/100,B=Acc%100; printf("%lld.",A);
        if (B<10) printf("0%lld%% ",B); else printf("%lld%% ",B);
        int up=320*a+300*b+200*c+100*d+50*e,dn=320*(a+b+c+d+e+f);
        // up/dn<=4/5
        if (5*up<=4*dn) printf("0\n"); else
        {
            //up/dn-4/5=(5*up-4*dn)/(5*dn)
            int pp=round(1.0L*(5*ppmax*(5*up-4*dn))/(5*dn));
            printf("%lld\n",pp);
        }
    }
    return 0;
}

C. 連方

徐神開場寫的,我題目都沒看

#include <bits/stdc++.h>

void work() {
    int n; std::cin >> n;
    std::string a, b; std::cin >> a >> b;
    std::string full(n, '#'), empty(n, '.'), ans[7];
    if(a == full && b == full) {
        std::cout << "Yes\n";
        for(int i = 0; i < 7; ++i) std::cout << full << char(10);
        return ;
    }
    if(a == empty || b == empty) {
        std::cout << "Yes\n";
        if(a == empty) for(int i = 0; i < 7; ++i) std::cout << b << char(10);
        else           for(int i = 0; i < 7; ++i) std::cout << a << char(10);
        return ;
    }
    if(a == full || b == full) {
        std::cout << "No\n";
        return ;
    }
    ans[0] = a, ans[6] = b;
    for(int i = 1; i < 6; ++i) ans[i] = empty;
    int l, r;
    for(int i = 0; i < n; ++i) {
        if(a[i] == '#') ans[1][i] = '.'; else ans[1][i] = '#';
        if(b[i] == '#') ans[5][i] = '.'; else ans[5][i] = '#';
    }
    for(int i = 0; i < n; ++i) {
        if(ans[1][i] == '#') continue;
        if(i > 0 && ans[1][i - 1] == '#' || i < n - 1 && ans[1][i + 1] == '#') {
            ans[2][l = i] = '#';
            break;
        }
    }
    for(int i = 0; i < n; ++i) {
        if(ans[5][i] == '#') continue;
        if(i > 0 && ans[5][i - 1] == '#' || i < n - 1 && ans[5][i + 1] == '#') {
            ans[4][r = i] = '#';
            break;
        }
    }
    if(l > r) std::swap(l, r);
    if(std::abs(l - r) <= 1) ans[3][l] = '#'; else {
        for(int i = l + 1; i <= r - 1; ++i) ans[3][i] = '#';
    }

    std::cout << "Yes\n";
    for(int i = 0; i < 7; ++i) std::cout << ans[i] << char(10);
    return ;
}

int main() {
    std::ios::sync_with_stdio(false);
    int T; std::cin >> T; while(T--) work();
    return 0;
}


D. 有限小數

徐神和祁神一直在討論這個題,我當時在想+寫 H 就沒咋參與

本來一直 WA 最後神秘改了下列舉的上界就過了,十分神奇

#include <bits/stdc++.h>

using i128 = __int128_t;
using llsi = long long;

constexpr int c2 = 40, c5 = 20;

template <typename T>
T myabs(T a) {
    return a < 0 ? -a : a;
}

i128 p2[101] = {1}, p5[101] = {1};
i128 w;

i128 exgcd(i128 a, i128 b, i128 &x, i128 &y) {
    if (0==b) { x = 1; y = 0; return a; }
    i128 ret = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return ret;
}

i128 calc(i128 a, i128 b, i128 c) {
    i128 x0, y0;
    i128 g = exgcd(a, b, x0, y0);
    // assert(a * x0 + b * y0 == g);
    if (c % g != 0) return -1;
    x0 *= c / g, y0 *= c / g;
    i128 val = myabs(b / g);
    x0 = (x0 % val + val) % val;
    // if (0 == x0) x0 = val;
    return x0;
}

void work() {
    llsi _a, _b; std::cin >> _a >> _b;
    i128 a = _a, b = _b;
    llsi c, d = -1;
    for(int x = 0; x <= c2; ++x) for(int y = 0; y <= c5; ++y) {
        if(b * w % (p2[x] * p5[y]) != 0) break;
        i128 nd = b * w / (p2[x] * p5[y]), nc = calc(- p2[x] * p5[y], b, a * w);
        if(nc < 0) continue;
        if(nd > 1'000'000'000) continue;
        // std::cerr << std::format("x = {}, y = {}, c = {}, d = {}\n", p2[x], p5[y], nc, nd);
        if(d < 0 || nc < c || nc == c && nd < d) c = nc, d = nd;
    }

    std::cout << c << " " << d << std::endl;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    for(int i = 1; i <= std::max(c2, c5); ++i) p2[i] = p2[i - 1] * 2, p5[i] = p5[i - 1] * 5;
    w = p2[30] * p5[13];
    // std::cout << i128(w) << char(10);
    int T; std::cin >> T; while(T--) work();
    return 0;
}


E. 合成大西瓜

手玩一波會發現答案就是所有度數不為一的點權值的最大值;以及所有度數為一的點權值的次大值;再求一個 \(\max\) 即可

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

const int N = 1e5+5;
int n, m, A[N], deg[N], mx2, mx1, sd1;
signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> m;
    for (int i=1; i<=n; ++i) cin >> A[i];
    for (int i=1; i<=m; ++i) {
        int x, y; cin >> x >> y;
        ++deg[x], ++deg[y];
    }

    if (1==n) {
        cout << A[1] << '\n';
        return 0;
    }

    mx2 = mx1 = sd1 = -1;
    for (int i=1; i<=n; ++i) {
        if (1==deg[i]) {
            if (A[i]>=mx1) sd1 = mx1, mx1 = A[i];
            else if (A[i]>sd1) sd1 = A[i];
        } else {
            mx2 = max(mx2, A[i]);
        }
    }

    cout << max(mx2, sd1) << '\n';

    return 0;
}

H. str(list(s))

想清楚了其實很好寫,注意到只有 ' 是特殊的,因此我們需要特別記錄 的個數

\(f_{i,j}\) 表示 \(s^i\) 下標模 \(p\) 後為 \(j\) 的所有非 ' 字元的貢獻;\(g_{i,j}\) 表示 \(s^i\) 下標模 \(p\) 後為 \(j\)' 數量;\(num_{i,j}\) 表示 \(s^i\) 下標模 \(p\) 後為 \(j\) 的所有字元的數量

轉移的時候按照題目模擬一下即可,注意特判開頭和結尾的變化,複雜度 \(O(|S|+kp)\)

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,M=3005,mod=1e9+7;
char s[N]; int k,p,f[M][M],g[M][M],num[M][M];
inline void inc(int& x,CI y)
{
    if ((x+=y)>=mod) x-=mod;
}
int main()
{
    scanf("%s%d%d",s,&k,&p);
    int n=strlen(s);
    for (RI i=0;i<n;++i)
    {
        ++num[0][i%p];
        if (s[i]=='\'') ++g[0][i%p]; else inc(f[0][i%p],s[i]);
    }
    n=(n-1+p)%p;
    for (RI i=0;i<k;++i)
    {
        inc(f[i+1][0],(1LL*(num[i][0]-1+mod)%mod*' '%mod+'[')%mod);
        inc(f[i+1][(5*n+4)%p],(1LL*(num[i][n]-1+mod)%mod*','%mod+']')%mod);
        for (RI j=0;j<p;++j)
        {
            inc(f[i+1][(5*j+2)%p],f[i][j]);
            // inc(f[i+1][(5*j+1)%p],1LL*(num[i][j]-g[i][j]+mod)*'\''%mod);
            // inc(f[i+1][(5*j+3)%p],1LL*(num[i][j]-g[i][j]+mod)*'\''%mod);
            inc(g[i+1][(5*j+1)%p],(num[i][j]-g[i][j]+mod)%mod);
            inc(g[i+1][(5*j+3)%p],(num[i][j]-g[i][j]+mod)%mod);
            if (j!=0) inc(f[i+1][(5*j+0)%p],1LL*num[i][j]*' '%mod);
            if (j!=n) inc(f[i+1][(5*j+4)%p],1LL*num[i][j]*','%mod);
        }
        for (RI j=0;j<p;++j)
        {
            inc(g[i+1][(5*j+2)%p],g[i][j]);
            inc(f[i+1][(5*j+1)%p],1LL*g[i][j]*'"'%mod);
            inc(f[i+1][(5*j+3)%p],1LL*g[i][j]*'"'%mod);
        }
        for (RI j=0;j<p;++j) for (RI k=0;k<5;++k)
        inc(num[i+1][(5*j+k)%p],num[i][j]);
        n=(5*n+4)%p;
    }
    for (RI i=0;i<p;++i)
    printf("%d ", int((f[k][i]+1LL*g[k][i]*'\''%mod)%mod));
    // printf("%c", char((f[k][i]+1LL*g[k][i]*'\''%mod)%mod));
    return 0;
}

I. 算術

注意到除了 \(1\) 之外的所有卡片一定是直接乘起來最優,現在的問題就是 \(1\) 要怎麼使用

一種是拿兩個 \(1\) 湊出一個新的 \(2\);另一種是不停地把剩下的 \(1\) 加到做乘法的數中最小的上面

由於 \(a_i\le 100\),我們可以直接列舉新生成了多少個 \(2\),剩下的部分模擬一下找個最優的方案即可

現在的問題是怎麼比較兩種方案的大小,由於式子是連乘的形式因此直接取對數後相加即可

#include<cstdio>
#include<iostream>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
const int N=15,mod=998244353;
int t,a[N],b[N]; long double lg[105];
int main()
{
    for (RI i=1;i<=100;++i) lg[i]=logl(i);
    for (scanf("%d",&t);t;--t)
    {
        for (RI i=1;i<=9;++i) scanf("%d",&a[i]);
        long double mx=-1; int cs;
        for (RI i=0;2*i<=a[1];++i)
        {
            for (RI j=1;j<=9;++j) b[j]=a[j];
            b[1]-=2*i; b[2]+=i;
            for (RI j=2;j<9&&b[1];++j)
            {
                int tmp=min(b[1],b[j]);
                b[1]-=tmp; b[j]-=tmp; b[j+1]+=tmp;
            }
            long double cur=0;
            if (b[1]>0)
            {
                if (b[9]==0) cur=0; else
                {
                    int add=b[1]/b[9],rem=b[1]%b[9];
                    cur+=lg[9+add+1]*rem;
                    cur+=lg[9+add]*(b[9]-rem);
                }
            } else
            {
                for (RI j=2;j<=9;++j) cur+=lg[j]*b[j];
            }
            if (cur>mx) mx=cur,cs=i;
        }
        for (RI i=1;i<=9;++i) b[i]=a[i];
        b[1]-=2*cs; b[2]+=cs;
        for (RI i=2;i<9&&b[1];++i)
        {
            int tmp=min(b[1],b[i]);
            b[1]-=tmp; b[i]-=tmp; b[i+1]+=tmp;
        }
        int ans=1;
        if (b[1]>0)
        {
            if (b[9]==0) ans=1; else
            {
                int add=b[1]/b[9],rem=b[1]%b[9];
                for (RI i=1;i<=rem;++i) ans=1LL*ans*(9+add+1)%mod;
                for (RI i=1;i<=b[9]-rem;++i) ans=1LL*ans*(9+add)%mod;
            }
        } else
        {
            for (RI i=2;i<=9;++i)
            for (RI j=1;j<=b[i];++j) ans=1LL*ans*i%mod;
        }
        printf("%d\n",ans);
    }
    return 0;
}

J. 骰子

純簽到,直接 Gauss 一定每個面都能湊到 \(6\) 即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    return printf("%d",6*n*m),0;
}

K. 小 C 的神秘圖形

看了眼就扔給祁神了,然後被祁神秒了

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

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int n; cin >> n;
    string str1, str2;
    cin >> str1 >> str2;
    bool ok=true;
    for (int i=0; i<n; ++i) {
        if (str1[i]!='1' && str2[i]!='1') {ok=false; break;}
    }
    cout << (ok ? "1\n" : "0\n");
    return 0;
}

Postscript

這場感覺題太偏向於計數了,導致後期基本沒啥事情幹,怪不得 downvote 這麼多

據說 BIT 也很喜歡出計數,希望昆明能來點適合牢閃的題的說

相關文章