C Fall with Trees
题意:给定一个完全二叉树的根节点与其左右儿子的坐标,按照每层等间距、每一层相邻儿子均等间隔排布,问这棵二叉树围成的凸包面积。
解法:记第二层的总宽度为 b 2 = x b_2=x b2=x,则第三层宽度为 b 3 = x ( 1 + 1 2 ) \displaystyle b_3=x(1+\frac{1}{2}) b3=x(1+21),第四层宽度为 b 4 = x ( 1 + 1 2 + 1 4 ) b_4=\displaystyle x(1+\frac{1}{2}+\frac{1}{4}) b4=x(1+21+41),依次类推有第 i i i 层的宽度为 b i = x ( ∑ j = 0 i − 2 1 2 j ) = x ( 2 − 1 2 i − 2 ) \displaystyle b_i=x(\sum_{j=0}^{i-2}\frac{1}{2^j})=x(2-\frac{1}{2^{i-2}}) bi=x(j=0∑i−22j1)=x(2−2i−21)。
记层高为 h h h,则总面积为 1 2 h × [ ∑ i = 2 n ( b i + b i − 1 ) ] = h x ( 2 n − 5 + 1 2 n − 1 + 1 2 n − 2 ) \displaystyle \frac{1}{2}h \times [\sum_{i=2}^{n}(b_i+b_{i-1})]=hx(2n-5+\frac{1}{2^{n-1}}+\frac{1}{2^{n-2}}) 21h×[i=2∑n(bi+bi−1)]=hx(2n−5+2n−11+2n−21),带入公式即可。
D Link with Balls
题意:有 2 n 2n 2n 个箱子,第 2 i − 1 2i-1 2i−1 个箱子可以拿 k i ki ki 个球, k ∈ Z k \in \Z k∈Z,第 2 i − 1 2i-1 2i−1 个箱子至多拿 i i i 个球。问拿出 m m m 个球的方案数。
解法:考虑生成函数,记 f ( x ) f(x) f(x) 为方案数的生成函数,对于第 2 i − 1 2i-1 2i−1 个箱子,其生成函数 g i ( x ) = ∑ j = 1 ∞ x i j = 1 1 − x i \displaystyle g_i(x)=\sum_{j=1}^{\infty} x^{ij}=\frac{1}{1-x^i} gi(x)=j=1∑∞xij=1−xi1,对于第 2 i 2i 2i 个箱子,其生成函数 h i ( x ) = ∑ j = 0 i x j = 1 − x i + 1 1 − x \displaystyle h_i(x)=\sum_{j=0}^{i}x^j=\frac{1-x^{i+1}}{1-x} hi(x)=j=0∑ixj=1−x1−xi+1。 f ( x ) = ∏ i = 1 n g i ( x ) ∏ i = 1 n h i ( x ) = 1 1 − x 1 − x 2 1 − x 1 1 − x 2 1 − x 3 1 − x ⋯ 1 1 − x n 1 − x n + 1 1 − x = 1 − x n + 1 ( 1 − x ) n + 1 \displaystyle f(x)=\prod_{i=1}^ng_i(x)\prod_{i=1}^nh_i(x)=\frac{1}{1-x}\frac{{\cancel{1-x^2}}}{1-x} \frac{1}{\cancel{1-x^2}}\frac{\cancel{1-x^3}}{1-x} \cdots \frac{1}{\cancel{1-x^n}}\frac{1-x^{n+1}}{1-x}=\frac{1-x^{n+1}}{(1-x)^{n+1}} f(x)=i=1∏ngi(x)i=1∏nhi(x)=1−x11−x1−x2 1−x2 11−x1−x3 ⋯1−xn 11−x1−xn+1=(1−x)n+11−xn+1。
考虑 1 ( 1 − x ) n + 1 \displaystyle \frac{1}{(1-x)^{n+1}} (1−x)n+11 的无穷级数展开。首先 f ( x ) = 1 1 − x = 1 + x + x 2 + ⋯ + x n + ⋯ \displaystyle f(x)=\frac{1}{1-x}=1+x+x^2+\cdots+x^n+\cdots f(x)=1−x1=1+x+x2+⋯+xn+⋯,对等式两侧求 n n n 阶导数有 f ( n ) ( x ) = n ! 1 ( 1 − x ) n + 1 = n ! + ( n + 1 ) ! 1 ! x + ( n + 2 ) ! 2 ! x 2 + ⋯ + ( n + m ) ! m ! x m + ⋯ \displaystyle f^{(n)}(x)=n!\frac{1}{(1-x)^{n+1}}=n!+\frac{(n+1)!}{1!}x+\frac{(n+2)!}{2!}x^2+\cdots+\frac{(n+m)!}{m!}x^m+\cdots f(n)(x)=n!(1−x)n+11=n!+1!(n+1)!x+2!(n+2)!x2+⋯+m!(n+m)!xm+⋯。因而 x m x^m xm 项的系数等于 ( n + m ) ! n ! m ! = ( n + m n ) \displaystyle \frac{(n+m)!}{n!m!}=\dbinom{n+m}{n} n!m!(n+m)!=(nn+m)。答案等于 x m x^m xm 项系数减去 x m − n − 1 x^{m-n-1} xm−n−1 项系数,即 ( n + m n ) − ( m − 1 n ) \dbinom{n+m}{n}-\dbinom{m-1}{n} (nn+m)−(nm−1)。
E Link with EQ
题意:有一张长度为 n n n 的桌子,第一个人随机选取座位,从第二个人开始选取距离已经就坐的所有人最远的一个位置就坐。当最远距离仅为 1 1 1 时停止就坐,统计就坐人数。问就坐人数的期望。
解法:首先考虑一个 DP—— f i f_i fi 表示长度为 i i i 的桌子,两侧都坐着人时最多能坐多少人。那么这个递推非常容易—— f i = f ⌊ i 2 ⌋ + f i − 1 − ⌊ i 2 ⌋ + 1 f_i=f_{\left \lfloor \frac{i}{2}\right \rfloor}+ f_{i-1-\left \lfloor \frac{i}{2}\right \rfloor}+1 fi=f⌊2i⌋+fi−1−⌊2i⌋+1。其含义为,新来的一个人只能就坐于最中间的位置,即 ⌊ i 2 ⌋ \displaystyle \left\lfloor \frac{i}{2} \right \rfloor ⌊2i⌋ 的位置。那么剩下的位置被切分成为了两个新的两侧都坐有人的情况—— [ 1 , ⌊ i 2 ⌋ ] \displaystyle \left[1,\left\lfloor \frac{i}{2} \right \rfloor \right] [1,⌊2i⌋] 与 [ ⌊ i 2 ⌋ + 1 , i ] \displaystyle \left[\left\lfloor \frac{i}{2} \right\rfloor+1,i\right] [⌊2i⌋+1,i]。因而可以递推。
考虑 g i g_i gi 表示只有单侧坐着人的情况——那么 g i = f i − 1 + 1 , i ≥ 2 g_i=f_{i-1}+1,i\geq 2 gi=fi−1+1,i≥2。特别的, g 1 = 0 g_1=0 g1=0。如果单侧坐着人,那么下一个来的人就只能坐在另一头,然后情况回归到上面的情况。
那么答案呼之欲出了——枚举第一个人所处的位置, h n = 1 n ∑ i = 1 n g ( i − 1 ) + g ( n − i ) + 1 = 1 + 2 n ∑ i = 1 n g ( i − 1 ) \displaystyle h_n=\frac{1}{n}\sum_{i=1}^n g(i-1)+g(n-i)+1=1+\frac{2}{n}\sum_{i=1}^ng(i-1) hn=n1i=1∑ng(i−1)+g(n−i)+1=1+n2i=1∑ng(i−1)。使用前缀和优化即可做到 O ( 1 ) O(1) O(1) 查询。
#include <cstdio>
#include <algorithm>
using namespace std;
const long long mod = 1000000007ll;
const int N = 1000000;
long long power(long long a,long long x)
{
long long ans = 1;
while(x)
{
if(x&1)
ans = ans * a % mod;
a = a * a % mod;
x >>= 1;
}
return ans;
}
long long inv(long long x)
{
return power(x, mod - 2);
}
long long f[N + 5], g[N + 5], sumg[N + 5];
int main()
{
int t, n;
f[1] = f[2] = 0;
for (int i = 3; i <= N;i++)
{
int half = (i - 1) / 2;
f[i] = f[half] + f[i - 1 - half] + 1;
}
for (int i = 2; i <= N;i++)
g[i] = f[i - 1] + 1;
for (int i = 1; i <= N;i++)
sumg[i] = (sumg[i - 1] + g[i]) % mod;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
printf("%lld\n", (1 + 2ll * inv(n) * sumg[n - 1] % mod) % mod);
}
return 0;
}
F Link with Grenade
题意:有一个人向任意方向等概率抛掷一枚炸弹, t t t 时刻后爆炸,若 t t t 时刻炸弹离他不足 r r r 的距离则会被炸死,问被炸死的概率。
解法:任意方向等概率即以投掷点为中心做一球面,球面上各点等概率。因而不是水平投掷角等概率——这其实是不等概率的。真正的等概率应该放在球面上处理。
记投掷方向与 z z z 轴正向成 θ \theta θ 角。则由抛体运动公式, x = v t sin θ , y = v cos θ t − 1 2 g t 2 \displaystyle x=vt\sin \theta,y=v\cos \theta t-\frac{1}{2}gt^2 x=vtsinθ,y=vcosθt−21gt2。 r ≥ x 2 + y 2 = v 2 t 2 + g 2 t 4 4 − v g t 3 cos θ \displaystyle r\geq \sqrt{x^2+y^2}=\sqrt{v^2t^2+\frac{g^2t^4}{4}-vgt^3\cos \theta} r≥x2+y2=v2t2+4g2t4−vgt3cosθ。则可推出 cos θ ≤ 4 r 2 − 4 v 2 t 2 − g 2 t 4 4 v g t 3 \displaystyle \cos \theta\leq \frac{4r^2-4v^2t^2-g^2t^4}{4vgt^3} cosθ≤4vgt34r2−4v2t2−g2t4 为一有理数。
首先判断一定炸死或者一定不炸死的情况——当 4 r 2 − 4 v 2 t 2 − g 2 t 4 ≥ 4 v g t 3 4r^2-4v^2t^2-g^2t^4\geq 4vgt^3 4r2−4v2t2−g2t4≥4vgt3 时一定炸不死,当 4 r 2 − 4 v 2 t 2 − g 2 t 4 ≤ − 4 v g t 3 4r^2-4v^2t^2-g^2t^4\leq -4vgt^3 4r2−4v2t2−g2t4≤−4vgt3 时一定炸死。考察球冠的表面积—— S = π r 2 ( 1 − cos θ ) S=\pi r^2(1-\cos \theta) S=πr2(1−cosθ) 可知 cos θ \cos \theta cosθ 为等概率(注意 θ \theta θ 不是等概率!),概率 p = S 4 π r 2 = 1 − cos θ 2 \displaystyle p=\frac{S}{4\pi r^2}=\frac{1-\cos \theta}{2} p=4πr2S=21−cosθ。带入上面算好的 cos θ \cos \theta cosθ 即可。
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const double pi = acos(-1);
const int g = 10;
const long long mod = 1000000007ll;
long long power(long long a,long long x)
{
long long ans = 1;
while(x)
{
if(x&1)
ans = ans * a % mod;
a = a * a % mod;
x >>= 1;
}
return ans;
}
long long inv(long long x)
{
return power(x, mod - 2);
}
int main()
{
long long T, t, v, r;
scanf("%lld", &T);
while(T--)
{
scanf("%lld%lld%lld", &t, &v, &r);
long long up = 25 * t * t * t * t - r * r + v * v * t * t;
long long down = g * t * t * t * v;
if(up<=-down || up>=down)
{
if(up<0)
printf("0\n");
else
printf("1\n");
continue;
}
printf("%lld\n", ((1 + (up % mod * inv(down) % mod) % mod) % mod * inv(2) % mod + mod) % mod);
}
return 0;
}
G Link with Limit
题意:给定一个定义域与值域均在 { 1 , 2 , … , n } \{ 1,2,\dots,n\} {1,2,…,n} 上的函数 f ( x ) f(x) f(x),记 f 1 ( x ) = f ( x ) f_1(x)=f(x) f1(x)=f(x), f i ( x ) = f i − 1 ( f ( x ) ) , i ≥ 2 f_{i}(x)=f_{i-1}(f(x)),i\geq 2 fi(x)=fi−1(f(x)),i≥2,判断 ∀ i ∈ [ 1 , n ] \forall i \in [1,n] ∀i∈[1,n], lim j → ∞ 1 j ∑ k = 1 j f k ( i ) \displaystyle \lim_{j \to \infty} \frac{1}{j}\sum_{k=1}^{j}f_k(i) j→∞limj1k=1∑jfk(i) 是否相等。
解法:容易发现只要存在环,则环上的点与能到达这个环的点的所求值均为环上所有点的均值。因而拓扑排序找到所有的环,求出每一个环上值的均值,判断是否相等即可。
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100000;
int a[N + 5], in[N + 5];
int main()
{
int t, n;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
for (int i = 1; i <= n;i++)
in[i] = 0;
for (int i = 1; i <= n;i++)
{
scanf("%d", &a[i]);
in[a[i]]++;
}
queue<int> q;
for (int i = 1; i <= n;i++)
if(!in[i])
q.push(i);
while(!q.empty())
{
int tp = q.front();
q.pop();
in[a[tp]]--;
if(!in[a[tp]])
q.push(a[tp]);
}
long long siz = 0, sum = 0;
bool flag = 1;
for (int i = 1; i <= n;i++)
{
if(!in[i])
continue;
long long temp_siz = 0, temp_sum = 0;
while(in[i])
{
temp_siz++;
temp_sum += i;
in[i] = 0;
i = a[i];
}
if(!sum)
{
sum = temp_sum;
siz = temp_siz;
}
else
if(sum*temp_siz!=temp_sum*siz)
{
flag = 0;
break;
}
}
if(flag)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
J Yiwen with Formula
题意:给定一个长度为 n n n 的序列 { a n } \{ a_n\} {an},找到其全部的子序列(允许不连续)的和的乘积。即, ∏ k = 1 n ∑ i = 1 k a b k \displaystyle \prod_{k=1}^{n} \sum_{i=1}^{k}a_{b_k} k=1∏ni=1∑kabk,满足 1 ≤ b 1 < b 2 < ⋯ < b k ≤ n 1\leq b_1<b_2< \cdots<b_k \leq n 1≤b1<b2<⋯<bk≤n。 n ≤ 1 × 1 0 5 n \leq 1\times 10^5 n≤1×105, 0 ≤ a i ≤ 1 × 1 0 5 0 \leq a_i \leq 1\times 10^5 0≤ai≤1×105, ∑ a i ≤ 1 × 1 0 5 \sum a_i \leq 1\times 10^5 ∑ai≤1×105。
解法:考虑到 ∑ a i ≤ 1 × 1 0 5 \sum a_i \leq 1\times 10^5 ∑ai≤1×105,因而不妨假设和为 x x x 出现的次数为 f x f_x fx,则最终答案为 ∏ i = 1 ∑ a i x f x \displaystyle \prod_{i=1}^{\sum a_i} x^{f_x} i=1∏∑aixfx。若 a i = 0 a_i=0 ai=0 则答案直接为 0 0 0,因而下问不再赘述这种特殊情况。
考虑 f x f_x fx 如何快速求得。显然一个非常容易想到的方法就是 O ( n 2 ) O(n^2) O(n2) 的 01 背包。我们尝试模拟一下这一过程。对于第 i i i 个数,可以选择也可以不选择, f x ← f x + f x − a i f_x \leftarrow f_x+f_{x-a_i} fx←fx+fx−ai。从整个数组来看,其本质为整个数组向右平移 a i a_i ai 位后再加回到原数组。使用生成函数来观察这一过程—— g ( x ) = ∑ i = 0 ∑ a i f i x i \displaystyle g(x)=\sum_{i=0}^{\sum a_i} f_i x^i g(x)=i=0∑∑aifixi,则经过第 i i i 个数之后, g ( x ) ← g ( x ) + x a i g ( x ) g(x) \leftarrow g(x)+x^{a_i}g(x) g(x)←g(x)+xaig(x)。记初始的 g ( x ) = 1 g(x)=1 g(x)=1,则总的生成函数可以记为 ∏ i = 1 n ( 1 + x a i ) \displaystyle \prod_{i=1}^n(1+x^{a_i}) i=1∏n(1+xai)。我们只需要找到它的每一个系数即可。
注意到我们求出来的系数都是作为指数出现的,因而它们应当对 φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p−1 取模。这一过程可以套用 MTT (任意模数多项式乘法)的板子在 O ( n log n ) O(n \log n) O(nlogn) 时间内实现。最后逐项乘起来即可。
#include <bits/stdc++.h>
#define fp(i,a,b) for(int i=(a),_##i=(b)+1;i<_##i;++i)
#define fd(i,a,b) for(int i=(a),_##i=(b)-1;i>_##i;--i)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
using namespace std;
const int N = 2e5 + 5, P = 998244353;
using db = double;
using arr = int[N];
using ll = long long;
/*---------------------------------------------------------------------------*/
vector<int>inv;
int getInv(int L){
if(inv.empty())inv={0,1};
fp(i,inv.size(),L)inv.push_back((ll)(P-P/i)*inv[P%i]%P);
return inv[L];
}
int POW(ll a,int b,ll x=1){for(;b>=1;b>>=1,a=a*a%P)if(b&1)x=x*a%P;return x;}
int INV(int a){return a<N?getInv(a):POW(a,P-2);}
#define ADD(a, b) ((a) += (b), (a) >= P ? (a) -=P : 0) // (a += b) %= P
#define SUB(a, b) ((a) -= (b), (a) < 0 ? (a) += P: 0) // ((a -= b) += P) %= P
#define ADDE(a, b, c) ((a) = (b), ADD(a, c)) // a = (b + c) % P
#define SUBE(a, b, c) ((a) = (b), SUB(a, c)) // a = (b - c + P) % P
namespace MTT {
long double const pi = acos(-1);
struct comp {
long double r, i;
comp() { r = i = 0; }
comp(long double x, long double y) { r = x, i = y; }
comp conj() { return comp(r, -i); }
friend comp operator+(comp x, comp y) { return comp(x.r + y.r, x.i + y.i); }
friend comp operator-(comp x, comp y) { return comp(x.r - y.r, x.i - y.i); }
friend comp operator*(comp x, comp y) { return comp(x.r * y.r - x.i * y.i, x.i * y.r + x.r * y.i); }
};
typedef long long ll;
int r[400005];
comp a[400005], b[400005], c[400005], d[400005];
void fft(comp *f, int n, int op) {
for (int i = 1; i < n; i++)r[i] = (r[i >> 1] >> 1) + ((i & 1) ? (n >> 1) : 0);
for (int i = 1; i < n; i++)if (i < r[i])swap(f[i], f[r[i]]);
for (int len = 2; len <= n; len <<= 1) {
int q = len >> 1;
comp wn = comp(cos(pi / q), op * sin(pi / q));
for (int i = 0; i < n; i += len) {
comp w = comp(1, 0);
for (int j = i; j < i + q; j++, w = w * wn) {
comp d = f[j + q] * w;
f[j + q] = f[j] - d;
f[j] = f[j] + d;
}
}
}
}
void mtt(vector<int>&f, vector<int>&g, vector<int>&h, int n, int p) {
for (int i = 0; i < n; i++)
a[i].r = (f[i] >> 15), a[i].i = (f[i] & 32767),
c[i].r = (g[i] >> 15), c[i].i = (g[i] & 32767);
fft(a, n, 1), fft(c, n, 1);
for (int i = 1; i < n; i++)b[i] = a[n - i].conj();
b[0] = a[0].conj();
for (int i = 1; i < n; i++)d[i] = c[n - i].conj();
d[0] = c[0].conj();
for (int i = 0; i < n; i++) {
comp
aa = (a[i] + b[i]) * comp(0.5, 0),
bb = (a[i] - b[i]) * comp(0, -0.5),
cc = (c[i] + d[i]) * comp(0.5, 0),
dd = (c[i] - d[i]) * comp(0, -0.5);
a[i] = aa * cc + comp(0, 1) * (aa * dd + bb * cc), b[i] = bb * dd;
}
fft(a, n, -1), fft(b, n, -1);
for (int i = 0; i < n; i++) {
int
aa = (ll) (a[i].r / n + 0.5) % p,
bb = (ll) (a[i].i / n + 0.5) % p,
cc = (ll) (b[i].r / n + 0.5) % p;
h[i] = ((1ll * aa * (1 << 30) + 1ll * bb * (1 << 15) + cc) % p + p) % p;
}
}
}
class Poly{
#define FF0 fp(i,0,L-1)
#define FF1 fp(i,0,F.L-1)
vector<int> a;
public:
Poly(const vector<int>&t){a=t;}
Poly(int n=0){a.assign(n+1,0);}
int deg(){return(int)a.size()-1;}
int&operator[](const int&x){return a[x];}
void resize(size_t n){a.resize(n);}
friend Poly operator*(Poly A,Poly B){
int L=A.deg()+B.deg()+1,n=1;
while(n<=L)n<<=1;
Poly C(n);A.resize(n),B.resize(n);
MTT::mtt(A.a,B.a,C.a,n,P-1);
return C.resize(L),C;
}
#undef FF0
#undef FF1
};
int n,a[N];
Poly divide(int L,int R){
if(L==R){
Poly k(a[L]);
k[0]=k[a[L]]=1;
return k;
}
int m=(L+R)>>1;
return divide(L,m)*divide(m+1,R);
}
void Solve(){
int ans=1;
fp(i,1,n)scanf("%d",a+i),ans*=(a[i]!=0);
Poly f=divide(1,n);
fp(i,1,f.deg())ans=POW(i,f[i],ans);
printf("%d\n",ans);
}
int main(){
#ifdef LOCAL
file("s");
#endif
scanf("%*d");
while(~scanf("%d",&n))
Solve();
return 0;
}