「ARC126F」Affine Sort
题目
点这里看题目。
分析
我们可以一眼看出,\(f(K)\) 本质上就是一个数列,因此我们记 \(f_k=f(k),k\in \mathbb N_+\)。
下面是令人震撼的步骤......使用 Stolz 定理,我们可以修改所求极限的形式:
为什么是对的?我也说不清楚。
这里使用到的 Stolz 定理只要求分母上的 \(b_n\) 严格单增且发散于 \(+\infty\),\(b_n=n^3\) 显然是一个符合条件的数列。
另外,\(b_n-b_{n-1}=3n^2-3n+1=O(3n^2)\)。所以当 \(n\rightarrow \infty\) 的时候,
应该可以用 \(3n^2\) 去替换 \(b_n-b_{n-1}\)。
关注这样一个差分的形式,我们可以设 \(g_n=f_n-f_{n-1}\)。根据题意容易得到 \(g_n\) 的组合含义。
因此我们只需要求:
另一个比较巧妙的转化,由于我们只关心相对关系,因此计算 \(g_n\) 的时候,我们可以考虑 \(\frac{1}{n}((aX+b)\bmod n)=(\frac{a}{n}X+\frac{b}{n})\bmod 1\)。这样的话,我们仍然只需要保证 \((\frac{a}{n}X+\frac{b}{n})\bmod 1\) 是单调的。为了方便,下面就设 \(\newcommand\flo[1]{\left\{#1\right\}}\flo x=x\bmod 1\)。
设 \(\alpha=\frac a n,\beta = \frac b n\)。当 \(n\rightarrow \infty\) 的时候,我们可以取出 \(D=\{(\alpha,\beta)|\alpha,\beta\in [0,1),\flo{\alpha X_1+\beta}<\flo{\alpha X_2+\beta}<\dots<\flo{\alpha X_N+\beta}\}\)。这样一来,我们所要求的其实就是 \(D\) 的面积:
从有理数是怎么过渡到实数的?我们应该是在隐式地做积分。对于任意的 \(n\),每个合法的 \((a,b)\) 其实对应的是一个面积为 \(n^{-2}\) 的小矩形,求和之后才会得到面积。
重新回来考虑 \((\alpha,\beta)\) 这个东西。由于我们总在考虑 \(\bmod 1\) 的结果,因此我们可以将 \([0,1)\) 看成一个环:
首先,画出一条线段表示 \([0,1)\);而后,将 0 和 1 粘在一起,这样就得到了需要的环。
那么对于 \(\flo{\alpha X_k+\beta},k=1,2,\dots,N\),\(\beta\) 的作用其实是保持间距不变地平移环上的 \(N\) 个点。假如对于某个 \(\alpha\),这 \(N\) 个点从某个位置开始逆时针看过去是有序的,那么我们就可以用 \(\beta\) 来将 0 移动到那个位置,从而令它们真正有序。
因此这种情况下,我们只需要让 0 落在 \(\flo {\alpha X_N}\) 到 \(\flo{\alpha X_1}\) 这个区间上即可,因此此时 \(\beta\) 的贡献是 \(\flo{\alpha(X_1-X_N)}\)。
我们可以敏锐地觉察到,对于所有合法的 \(\alpha\) 来求积分即可得到最终的面积。
合法的 \(\alpha\) 就是“保持大致有序”的 \(\alpha\)。而如何刻画这样的相对关系?我们可以使用距离之和。设 \(f_k(\alpha)\) 为 \(\flo{\alpha X_k}\) 到 \(\flo{\alpha X_{k\bmod N+1}}\) 的距离,那么如果这 \(N\) 个值可以保持大致有序,当且仅当有 \(\sum_{k=1}^{N}f_k(\alpha)=1\)。
现在我们来研究单个函数 \(h=\flo{ka}\)。下图展示了 \(k<0\) 和 \(k>0\) 的图像:
可以发现,\(h\) 的图像其实就是在某些点有 \(\pm 1\) 的跳变(跳变的位置就是 \(h(\alpha)=0\ or\ 1\) 的位置)的斜率为 \(k\) 的一次函数图像。而 \(\sum_{k=1}^Nf_k(\alpha)\) 的斜率一定是 0,所以最终它的图像看起来像若干段水平线段拼起来。
我们只需要找出所有的断点和对应的变化值就可以给出 \(\sum_{k=1}^N f_k\) 的图像,找出函数值为 1 的那些段求定积分 \(\int_{l}^r\flo{\alpha(X_1-X_N)}\mathrm d\alpha\) 即可。而断点显然就是以 \(k\) 为分母的那些分数的位置,由于实际问题中 \(k\) 是整数,这些分数就很好找了。
总共断点数为 \(O(\sum X)\),复杂度完全可以接受。
小结:
-
数学定理......不说了,不会也没有办法;
-
最巧妙的转化出现在,将余数转化为小数部分这一步骤。这个东西可以推广:
\[\frac{1}{c}(a\bmod b)=\frac{a}{c}\bmod \frac{b}{c} \]不知道这个有啥用,反正如果不需要具体地考虑余数值,而只关心相对大小,都可以尝试这个转化。 -
另外两个地方值得注意:
- 对于 \(\beta\) 的处理,通过“环”的模型我们可以直接计算 \(\beta\) 的贡献;
- 对于有序性的处理,我们通过“距离”来刻画它。其实这应该是一个挺通用的想法,尤其是在环上。
代码
#include <cstdio>
#include <algorithm>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
typedef long long LL;
const int mod = 998244353, inv2 = ( mod + 1 ) / 2;
const int MAXS = 1e6 + 5;
template <typename _T>
void read( _T &x )
{
x = 0; char s = getchar(); bool f = false;
while( ! ( '0' <= s && s <= '9' ) ) { 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>
void write( _T x )
{
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
inline int Mul( int, int );
inline int Inv( const int );
inline LL Gcd( LL x, LL y ) { for( LL z ; y ; z = x, x = y, y = z % y ); return x; }
struct Fraction
{
LL fz, fm;
Fraction(): fz( 0 ), fm( 1 ) {}
Fraction( LL Z ): fz( Z ), fm( 1 ) {}
Fraction& operator *= ( const Fraction &g ) { return *this = *this * g; }
Fraction( LL Z, LL M )
{
LL d = Gcd( Z, M );
if( d ) fz = Z / d, fm = M / d;
}
Fraction operator * ( const Fraction &g ) const
{
LL d1 = Gcd( fz, g.fm ), d2 = Gcd( fm, g.fz );
return Fraction( fz / d1 * g.fz / d2, fm / d2 * g.fm / d1 );
}
Fraction operator + ( const Fraction &g ) const
{
LL d = Gcd( fm, g.fm );
return Fraction( g.fm / d * fz + fm / d * g.fz, fm / d * g.fm );
}
Fraction operator - ( const Fraction &g ) const
{
LL d = Gcd( fm, g.fm );
return Fraction( g.fm / d * fz - fm / d * g.fz, fm / d * g.fm );
}
inline LL Floor() const { return fz / fm; }
inline LL Ceil() const { return ( fz + fm - 1 ) / fm; }
inline operator int() const { return Mul( fz % mod, Inv( fm % mod ) ); }
inline bool operator < ( const Fraction &g ) const { return fz * g.fm < fm * g.fz; }
inline bool operator == ( const Fraction &g ) const { return fz == g.fz && fm == g.fm; }
};
typedef std :: pair<Fraction, int> Point;
Point brk[MAXS];
int X[MAXS];
int N, tot = 0;
inline int Qkpow( int, int );
inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
inline int Sqr( const int x ) { return Mul( x, x ); }
inline int Qkpow( int base, int indx )
{
int ret = 1;
while( indx )
{
if( indx & 1 ) ret = Mul( ret, base );
base = Mul( base, base ), indx >>= 1;
}
return ret;
}
int Integral( Fraction lim )
{
int ret = 0;
if( X[1] > X[N] )
{
LL tmp = ( lim * Fraction( X[1] - X[N] ) ).Floor();
ret = Mul( tmp, Mul( Inv( Sub( X[1], X[N] ) ), inv2 ) );
int len = lim - Fraction( tmp, X[1] - X[N] );
ret = Add( ret, Mul( Mul( inv2, Sub( X[1], X[N] ) ), Sqr( len ) ) );
}
else
{
LL tmp = ( lim * Fraction( X[N] - X[1] ) ).Ceil();
ret = Mul( tmp, Mul( Inv( Sub( X[N], X[1] ) ), inv2 ) );
int len = Fraction( tmp, X[N] - X[1] ) - lim;
ret = Sub( ret, Mul( Mul( inv2, Sub( X[N], X[1] ) ), Sqr( len ) ) );
}
return ret;
}
int main()
{
read( N );
rep( i, 1, N ) read( X[i] );
X[N + 1] = X[1];
rep( i, 1, N )
if( X[i] < X[i + 1] )
rep( j, 1, X[i + 1] - X[i] )
brk[++ tot] = Point( Fraction( j, X[i + 1] - X[i] ), - 1 );
else
rep( j, 0, X[i] - X[i + 1] - 1 )
brk[++ tot] = Point( Fraction( j, X[i] - X[i + 1] ), + 1 );
Fraction nxt;
int y = 0, ans = 0;
brk[++ tot] = Point( 0, 0 );
brk[++ tot] = Point( 1, 0 );
std :: sort( brk + 1, brk + 1 + tot );
for( int i = 1, j ; i <= tot ; i = j )
{
for( j = i ; j <= tot && brk[i].first == brk[j].first ; j ++ );
for( int k = i ; k < j ; k ++ ) y += brk[k].second;
if( y == 1 && j < tot )
ans = Add( ans, Sub( Integral( brk[j].first ), Integral( brk[i].first ) ) );
}
write( Mul( Inv( 3 ), ans ) ), putchar( '\n' );
return 0;
}