[洛谷P4491] [HAOI2018]染色

本文解析了HAOI2018Round2第二题“染色”的算法思路,介绍了如何计算所有可能染色方案中小C的愉悦度总和,并详细解释了使用组合数学、容斥原理及快速傅立叶变换进行高效计算的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

洛谷题目链接:[HAOI2018]染色

题目背景

HAOI2018 Round2 第二题

题目描述

为了报答小 C 的苹果, 小 G 打算送给热爱美术的小 C 一块画布, 这块画布可 以抽象为一个长度为 \(N\) 的序列, 每个位置都可以被染成 \(M\) 种颜色中的某一种.

然而小 C 只关心序列的 \(N\) 个位置中出现次数恰好为 \(S\) 的颜色种数, 如果恰 好出现了 \(S\) 次的颜色有 \(K\) 种, 则小 C 会产生 \(W_k\) 的愉悦度.

小 C 希望知道对于所有可能的染色方案, 他能获得的愉悦度的和对 \(1004535809\) 取模的结果是多少.

输入输出格式

输入格式:

从标准输入读入数据. 第一行三个整数 \(N, M, S\).

接下来一行 \(M + 1\) 个整数, 第 \(i\) 个数表示 \(W_{i-1}\)​ .

输出格式:

输出到标准输出中. 输出一个整数表示答案.

输入输出样例

输入样例#1:

8 8 3
3999 8477 9694 8454 3308 8961 3018 2255 4910

输出样例#1:

524070430

输入样例#2:

https://www.luogu.org/paste/rxrv9utg

输出样例#2:

231524284

说明

特殊性质: \(\forall 1 \le i \le m, W_i = 0\)

对于 \(100\%\) 的数据, 满足 \(0 \le W_i < 1004535809\)Data

题解:\(f[i]\)表示出现次数恰好等于\(S\)的颜色个数大于等于\(i\)的方案数,从\(m\)种颜色中选出\(i\)种颜色的方案是\(C_m^i\),将染色的序列看做一个有可重元素的排列,那么方案数就是\[f[i]=C_m^i*\frac{n!}{(n-S*i)!(S!)^i}*(n-S*i)^{m-i}\]

之所以是大于等于,是因为式子的最后面那部分还可能有出现次数恰好等于\(S\)的颜色.

\(lim=min(\lfloor\frac{n}{s} \rfloor,m)\).

\(ans[i]\)表示出现次数恰好等于\(S\)的颜色个数恰好等于\(i\)的方案数,根据容斥,有:\[ans[i]=\sum_{j=i}^{lim}(-1)^{j-i}*C_j^i*f[j]\]

将式子中的组合数拆开\[ans[i]*i!=\sum_{j=i}^{lim}*\frac{(-1)^{j-i}}{(j-i)!}*\frac{f[j]}{j!}\]

可以发现这是一个卷积的形式,设\(A[i]=\frac{(-1)^{i}}{i!},B[i]=\frac{f[i]}{i}\),那么将\(A\)的系数翻转,再与\(B\)做多项式乘法就可以了.

最后在统计答案的时候要注意,因为将\(A\)的系数翻转了,所以求出来的结果相当于是\(C_{i+lim}=A^{'}_{lim+i-j}*B_j\),所以在统计出现\(i\)次的方案数时要将数组的下标加\(lim\).

#include<bits/stdc++.h>
using namespace std;
const int N = 1e7+5;
const int M = 3e5+5;
const int mod = 1004535809;

int n, m, s, lim, r[M], len = 0, f[M], ans = 0, w[N], a[N], cnt[N];
int pinv[N], inv[N], fac[N];

void init(int n){
    pinv[0] = inv[0] = fac[0] = pinv[1] = inv[1] = fac[1] = 1;
    for(int i = 2; i <= n; i++){
        fac[i] = 1ll*fac[i-1]*i%mod;
        inv[i] = 1ll*(mod-mod/i)*inv[mod%i]%mod;
        pinv[i] = 1ll*pinv[i-1]*inv[i]%mod;
    }
}

int C(int n, int m){ return 1ll*fac[n]*pinv[m]%mod*pinv[n-m]%mod; }

int qpow(int x, int n){
    int res = 1;
    for(; n; x = 1ll*x*x%mod, n >>= 1)
        if(n & 1) res = 1ll*res*x%mod;
    return res;
}

void NTT(int *A, int f){
    for(int i = 0; i < n; i++) if(i < r[i]) swap(A[i], A[r[i]]);
    for(int i = 1; i < n; i <<= 1){
        int wi = qpow(3, (mod-1)/(i << 1)), x, y;
        if(f == -1) wi = qpow(wi, mod-2);
        for(int j = 0; j < n; j += (i << 1)){
            for(int k = 0, w = 1; k < i; k++, w = 1ll*w*wi%mod){
                x = A[j+k], y = 1ll*A[i+j+k]*w%mod;
                A[j+k] = (x+y)%mod, A[i+j+k] = (x-y+mod)%mod;
            }
        }
    }
    if(f == -1){
        int invn = qpow(n, mod-2);
        for(int i = 0; i < n; i++) A[i] = 1ll*A[i]*invn%mod;
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin >> n >> m >> s, lim = min(m, n/s), init(max(n, m));
    for(int i = 0; i <= m; i++) cin >> w[i];
    for(int i = 0; i <= lim; i++)
        cnt[i] = 1ll*fac[n]*qpow(pinv[s], i)%mod*pinv[n-s*i]%mod*C(m, i)%mod*qpow(m-i, n-s*i)%mod*fac[i]%mod;
    for(int i = 0; i <= lim; i++) a[i] = (((lim-i)&1) ? (mod-pinv[lim-i]) : pinv[lim-i]);
    for(n = 1; n < (lim+1 << 1); n <<= 1) len++;
    for(int i = 0; i < n; i++) r[i] = (r[i>>1]>>1)|((i&1)<<len-1);
    NTT(cnt, 1), NTT(a, 1);
    for(int i = 0; i < n; i++) cnt[i] = 1ll*cnt[i]*a[i]%mod;
    NTT(cnt, -1);
    for(int i = 0; i <= lim; i++) (ans += 1ll*cnt[lim+i]*pinv[i]%mod*w[i]%mod) %= mod;
    cout << ans << endl;
    return 0;
}

转载于:https://www.cnblogs.com/BCOI/p/10492838.html

内容概要:文章基于4A架构(业务架构、应用架构、数据架构、技术架构),对SAP的成本中心和利润中心进行了详细对比分析。业务架构上,成本中心是成本控制的责任单元,负责成本归集与控制,而利润中心是利润创造的独立实体,负责收入、成本和利润的核算。应用架构方面,两者都依托于SAP的CO模块,但功能有所区分,如成本中心侧重于成本要素归集和预算管理,利润中心则关注内部交易核算和获利能力分析。数据架构中,成本中心与利润中心存在多对一的关系,交易数据通过成本归集、分摊和利润计算流程联动。技术架构依赖SAP S/4HANA的内存计算和ABAP技术,支持实时核算与跨系统集成。总结来看,成本中心和利润中心在4A架构下相互关联,共同为企业提供精细化管理和决策支持。 适合人群:从事企业财务管理、成本控制或利润核算的专业人员,以及对SAP系统有一定了解的企业信息化管理人员。 使用场景及目标:①帮助企业理解成本中心和利润中心在4A架构下的运作机制;②指导企业在实施SAP系统时合理配置成本中心和利润中心,优化业务流程;③提升企业对成本和利润的精细化管理水平,支持业务决策。 其他说明:文章不仅阐述了理论概念,还提供了具体的应用场景和技术实现方式,有助于读者全面理解并应用于实际工作中。
这道目还可以使用树状数组或线段树来实现,时间复杂度也为 $\mathcal{O}(n\log n)$。这里给出使用树状数组的实现代码。 解思路: 1. 读入数据; 2. 将原数列离散化,得到一个新的数列 b; 3. 从右往左依次将 b 数列中的元素插入到树状数组中,并计算逆序对数; 4. 输出逆序对数。 代码实现: ```c++ #include <cstdio> #include <cstdlib> #include <algorithm> const int MAXN = 500005; struct Node { int val, id; bool operator<(const Node& other) const { return val < other.val; } } nodes[MAXN]; int n, a[MAXN], b[MAXN], c[MAXN]; long long ans; inline int lowbit(int x) { return x & (-x); } void update(int x, int val) { for (int i = x; i <= n; i += lowbit(i)) { c[i] += val; } } int query(int x) { int res = 0; for (int i = x; i > 0; i -= lowbit(i)) { res += c[i]; } return res; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); nodes[i] = {a[i], i}; } std::sort(nodes + 1, nodes + n + 1); int cnt = 0; for (int i = 1; i <= n; ++i) { if (i == 1 || nodes[i].val != nodes[i - 1].val) { ++cnt; } b[nodes[i].id] = cnt; } for (int i = n; i >= 1; --i) { ans += query(b[i] - 1); update(b[i], 1); } printf("%lld\n", ans); return 0; } ``` 注意事项: - 在对原数列进行离散化时,需要记录每个元素在原数列中的位置,便于后面计算逆序对数; - 设树状数组的大小为 $n$,则树状数组中的下标从 $1$ 到 $n$,而不是从 $0$ 到 $n-1$; - 在计算逆序对数时,需要查询离散化后的数列中比当前元素小的元素个数,即查询 $b_i-1$ 位置上的值; - 在插入元素时,需要将离散化后的数列的元素从右往左依次插入树状数组中,而不是从左往右。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值