「CF1188E」Problem from Red Panda

题目

点这里看题目。


给定一个长度为 k 的非负整数序列 a

你可以对于 a 做如下操作任意次:

  • 选定 1jk,满足除了 aja 中其它数都为正

    而后,令 aj 加上 k1,令除了 aja 中其它数减去 1

    (这样一次操作被称为“操作 j”)

你需要求出经过任意次操作后,可能的 a 序列有多少种。答案对 998244353 取模。

所有数据满足 1k105,0ai106

分析

一上来竟然想到 DAG 游走上面去了

注意到,最终得到的序列(不妨记为 a和操作顺序无关,只和每个位置操作了多少次有关。具体来说,设 xi 表示“操作 i”总共执行的次数,那么有 ai=ai+kxitxt。在此,我们记 m=txt,则 ai=ai+kxim

考虑 x 序列即可帮助我们避免算重的问题。注意到,对于 k 个数全部操作一遍等于没有操作,所以值得考虑的 x 必须满足 minx=0。冷静一下,发现仅统计 minx=0 的序列也就不会算重了。具体证明如下:

Proof.

考虑两个表示操作次数序列 xy,满足 minx=miny=0xy。我们同时记 mx=txt,my=tyt

如果 mx=my,因为 xy,所以容易推出它们产生的结果序列不同。

如果 mxmy,不妨设 mx<my。反证,假设对于任意的下标 j,都有 aj+kxjmx=aj+kyjmy,于是 xj<yj于是 yj>xj0,所以 miny>0,矛盾。

现在我们已经解决了一半的问题,考虑另一半——即“是否存在一个可行的操作序列”的问题。

做一些数值上的观察。注意到 ai+kxim0ximax{mai,0}k。进一步地,应该有 m=ixiimax{mai,0}k

这个条件看起来还是不太强,因为它甚至弱于“a 中不能有两个及以上的 0”。但是,我们注意到进行任意多次操作后,这个条件都必须成立。也即,对于任意的 0tmtimax{tai,0}k 都应成立

这个条件够强吗?至少它不弱于“至多一个 0”。我们考虑建立归纳证明:

Conclusion.

对于操作次数序列 x,记 m=ixi,则存在一种合法且“操作 i”出现次数为 xi 的操作序列当且仅当 0tm,timax{tai,0}k

Proof.

对于 m 进行归纳。首先,m=0 意味着 x 全为 0,显然是成立的。

m>0 时,我们需要证明充分性和必要性:

必要性

我们只需要证明,存在一个 i0 满足 xi0>0 且“操作 i0”合法且操作后右侧条件仍然满足,即可生成一个操作序列。假设 i0 存在,我们设操作后的序列为 b

wt=imax{tai,0}k。任取 0t<m,有:

imax{tbi,0}k=ii0max{t+1ai,0}k+max{tai0k+1,0}k=wt+1max{t+1ai0,0}k+max{tai0k+1,0}k

如果 t+1ai0>0,那么就有 LHS=wt+11t;如果 t+1ai00,则 LHS=wt+1

贪心地想,我们令 i0使得 xi>0ai 最小的 i。注意到,如果 xi=0,则 根据 ximax{mai,0}k 可知 aim。于是,对于 t[0,ai0) 而言,wt+1 的和式中无论是 xi>0 的项还是 xi=0 的项贡献都必然为 0,于是 wt+1=0

那么,执行“操作 i0”合法吗?不合法的情况是,除了 ai0 以外还有数为 0。考虑 w11 即可知此情况不存在。

故必要性得证。

充分性

此处,我们沿用“必要性”证明的 i0

在此,我们不加证明地指出:

如果存在一种符合条件的操作序列,则必然存在一种操作序列,使得其第一次操作为“操作 i0”。

类似于“必要性”证明的讨论,可以证明 t[0,m)wt+1t+1。再讨论一下 t=0 的情况,可知 w0=0

故充分性得证。

证明完这个重要结论之后,我们只需要枚举 s 并计算方案数即可。s 确定时,方案数可以直接插板得出。而当 s>maxa 时,对于任意 xi 都有 xi>0,故 s 只需枚举到 maxa

复杂度因此为 O(maxa+klogk)

代码

#include <bits/stdc++.h>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int mod = 998244353;
const int MAXN = 2e6 + 5;

template<typename _T>
inline void Read( _T &x ) {
    x = 0; char s = getchar(); bool f = false;
    while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
    while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    if( f ) x = -x;
}

template<typename _T>
inline void Write( _T x ) {
    if( x < 0 ) putchar( '-' ), x = -x;
    if( 9 < x ) Write( x / 10 );
    putchar( x % 10 + '0' );
}

int fac[MAXN], ifac[MAXN];

int su[MAXN];
int A[MAXN];

int K, V;

inline int Qkpow( int, int );
inline int Inv( const int &a ) { return Qkpow( a, mod - 2 ); }
inline int Mul( int x, const int &v ) { return 1ll * x * v % mod; }
inline int Sub( int x, const int &v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, const int &v ) { return ( x += v ) >= mod ? x - mod : x; }

inline int& MulEq( int &x, const int &v ) { return x = 1ll * x * v % mod; }
inline int& SubEq( int &x, const int &v ) { return ( x -= v ) < 0 ? ( x += mod ) : x; }
inline int& AddEq( int &x, const int &v ) { return ( x += v ) >= mod ? ( x -= mod ) : x; }

inline int C( int n, int m ) { return n < m ? 0 : Mul( fac[n], Mul( ifac[m], ifac[n - m] ) ); }

inline int Qkpow( int base, int indx ) {
    int ret = 1;
    while( indx ) {
        if( indx & 1 ) MulEq( ret, base );
        MulEq( base, base ), indx >>= 1;
    }
    return ret;
}

inline void Down( int &x ) { x &= x - 1; }
inline void Up( int &x ) { x += x & ( - x ); }
inline void Update( int x, int v ) { for( ; x <= K ; Up( x ) ) su[x] += v; }
inline  int Query( int x ) { int ret = 0; for( ; x ; Down( x ) ) ret += su[x]; return ret; }

inline void Init( const int &n ) {
    fac[0] = 1; rep( i, 1, n ) fac[i] = Mul( fac[i - 1], i );
    ifac[n] = Inv( fac[n] ); per( i, n - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
}

int main() {
    Read( K );
    rep( i, 1, K )
        Read( A[i] ), V = std :: max( V, A[i] );
    std :: sort( A + 1, A + 1 + K );

    Init( V + K );
    int ans = 0, sub = 0;
    for( int s = 0, i = 1, j = 1 ; s <= V ; s ++ ) {
        for( ; i <= K && A[i] <= s ; i ++ )
            sub += A[i] / K, Update( A[i] % K + 1, +1 );
        for( ; j <= K && A[j] <  s ; j ++ );
        int low = s / K * ( i - 1 ) - sub + Query( s % K );
        if( low > s ) break;
        AddEq( ans, Sub( C( s - low + K - 1, K - 1 ), C( s - low + j - 2, K - 1 ) ) );
    }
    Write( ans ), putchar( '\n' );
    return 0;
}
posted @   crashed  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示