• AtCoder ABC198 F


    题目链接

    AtCoder Beginner Contest 198 F-Cube

    题意

    一个立方体的六个面上各写了一个正整数,问有多少种方案使得六个数之和为 (S)

    这里假定数字没有方向,即把立方体各种翻转的情况视为同一种方案。

    (6leq Sleq 10^{18})

    思路

    有三种做法:

    1. 利用 (Pacute{o}lya) 定理计算
    2. 组合数大力分类讨论
    3. 手推/ (OEIS) 生成函数

    太naïve了,赛时写的第二种,结果没有调完,这里主要讲第一种做法。

    立方体翻转总共有 (24) 种情况,记 (G) 为这些置换方式的群,根据 (Pacute{o}lya) 定理,答案为

    [frac{1}{|G|}sum_{gin G}c_0(g) ]

    那么我们便需要对于每个 (g),计算 (c_0(g)) 的大小,考虑有哪些情况,(24) 个置换其实可以手算,但是因为懒(没凑齐) 就打了个表,代码:

    #include<iostream>
    #include<cstring>
    #include<map>
    #include<vector>
    #define mem(a,b) memset(a,b,sizeof(a))
    #define rep(i,a,b) for(int i = (a); i <= (b); i++)
    #define per(i,b,a) for(int i = (b); i >= (a); i--)
    #define VI vector<int> 
    using namespace std;
    vector<VI> rotate;
    bool tf[7];
    map<VI, bool> vis;
    VI mul(int x, int y){
        VI a = rotate[x], b = rotate[y];
        rep(i,0,5) a[i] = b[a[i]-1];
        return a;
    }
    void print_circle(VI p){
        mem(tf, 0);
        rep(i,1,6){
            if(tf[i]) continue;
            cout<<"("<<i;
            int j = p[i-1];
            while(j != i) cout<<" "<<j, tf[j]=true, j = p[j-1];
            cout<<") ";
        }
        cout<<endl;
    }
    int main(){
        int t = 720;
        rotate.push_back({2, 6, 3, 4, 1, 5});
        rotate.push_back({2, 3, 1, 6, 4, 5});
        vis[rotate[0]] = true;
        vis[rotate[1]] = true;
        int j = 0;
        while(++j <= rotate.size()){
            print_circle(rotate[j-1]);
            VI nxt;
            rep(i,0,j-2) if(!vis[nxt = mul(j-1, i)]){
                vis[nxt] = true;
                rotate.push_back(nxt);
            }
        }
        return 0;
    }
    

    打表结果:

    (1 2 6 5) (3) (4)
    (1 2 3) (4 6 5)
    (1 6) (2 3) (4 5)
    (1 5 4) (2 3 6)
    (1 5 6 2) (3) (4)
    (1) (2 3 5 4) (6)
    (1 4 2) (3 5 6)
    (1 4 6 3) (2) (5)
    (1) (2) (3) (4) (5) (6)
    (1 4 5) (2 6 3)
    (1 4) (2 5) (3 6)
    (1 2) (3 4) (5 6)
    (1 6) (2) (3 4) (5)
    (1 5) (2 6) (3 4)
    (1) (2 5) (3 4) (6)
    (1 6) (2 5) (3) (4)
    (1 6) (2 4) (3 5)
    (1 5 3) (2 4 6)
    (1) (2 4 5 3) (6)
    (1 2 4) (3 6 5)
    (1 3 6 4) (2) (5)
    (1 3 5) (2 6 4)
    (1 3) (2 5) (4 6)
    (1 3 2) (4 5 6)
    

    可以注意到总共有 (5) 种形式:

    • (1+1+4) *6

    • (3+3) *8

    • (2+2+2) *6

    • (1+1+1+1+1+1) *1

    • (1+1+2+2) *3

    计算时预先将 (S) 减去 (6),把限制放宽到非负整数,前 (4) 种情况很好算,简单提一下第 (5) 种情况。

    (S) 中去掉两个单独的数之后还剩 (2k)("1+1") 的方案是 (S-2k+1) 种,("2+2") 的方案是 (k+1) 种,记 (c=frac{S}{2}),从而答案为

    [sum_{k=0}^c(k+1)(S-2k+1)\=sum_{k=0}^c((S+1)(k+1)-2k^2-2k)\=frac{(c+1)(c+2)}2(S+1)-frac{2c(c+1)(2c+1)}6-c(c+1)\=frac{(c+1)(c+2)}2(S+1)-frac{c(c+1)(2c+4)}6\=frac{(c+1)(c+2)(3(S+1)-4c)}6 ]

    最后把值求和再除以 (24) 即得所求答案。

    实现细节

    • 由于有些地方需要判断 (S) 的整除情况以及利用向下取整的除法,读入时不能直接对 (S) 取模。
    • 一些计算过程涉及到了减法,最后输出 (ans) 的时候不要忘记把它拉成正数。

    Code

    #include<iostream>
    #define rep(i,a,b) for(int i = (a); i <= (b); i++)
    #define per(i,b,a) for(int i = (b); i >= (a); i--)
    #define mod 998244353
    #define ll long long
    using namespace std;
    ll inv(ll x){
        ll ret = 1, pow = mod-2;
        for(; pow; pow >>= 1){
            if(pow&1) (ret *= x) %= mod;
            (x *= x) %= mod;
        }
        return ret;
    }
    ll C(int n, int m){
        ll ret = 1;
        per(i,n,n-m+1) (ret *= i) %= mod;
        rep(i,1,m) (ret *= inv(i)) %= mod;
        return ret;
    }
    int main(){
        ll S;
        cin >> S;
        S -= 6;
    
        ll tmp, ans = 0;
        // (1+1+4) *6
        ll s = (S/4) % mod;
        tmp = (s+1) * (S%mod+1-2*s) % mod;
        (ans += 6*tmp) %= mod;
    
        // (3+3) *8
        if(S%3 == 0){
            tmp = (S/3+1) % mod;
            (ans += 8*tmp) %= mod;
        }
    
        // (1+1+2+2) *3
        ll c = (S/2) % mod;
        tmp = (3*(S+1)%mod-4*c)%mod * (c+2)%mod * (c+1)%mod * inv(6)%mod;
        (ans += 3*tmp) %= mod;
    
        // (2+2+2) *6
        if(S%2 == 0){
            tmp = C((S/2+2)%mod, 2);
            (ans += 6*tmp) %= mod;
        }
    
        // (1+1+1+1+1+1) *1
        tmp = C((S+5)%mod, 5);
        (ans += tmp) %= mod;
    
        (ans *= inv(24)) %= mod;
        cout << (ans+mod)%mod << endl;
        return 0;
    }
    
  • 相关阅读:
    Android 的快速增长让一些开发人员担忧 狼人:
    Android程序开发入门教程 狼人:
    2010:Android 商机之年 狼人:
    控制力的较量 Android上的博弈 狼人:
    深度解析Windows Phone 7 开发 狼人:
    图解iPhone开发入门教程 狼人:
    用Android LiveCD体验Android 操作系统的魅力 狼人:
    Android 教程之实现动作感应技术 狼人:
    Android版多功能日历,欢迎大家测试 狼人:
    iPhone、Windows Mobile、Symbian、Android移动开发前景分析 狼人:
  • 原文地址:https://www.cnblogs.com/Neal-lee/p/14661926.html
Copyright © 2020-2023  润新知