今天做的太差了,全都写一写。
T1
题意
一棵有根树,每个点有一个颜色。离线询问,每次询问一个子树中第 k 大的颜色。
空间 16 MB。
思路
卡空间的题。主席树显然不行,但是 dsu on tree 却可以。
没有难度,没想到 dsu on tree 是我菜。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N<<1, Q = 3e5 + 10, inf = (1<<31)-1;
int n, m, vn, c[N];
vector<int> vec;
int h[N], ecnt, nxt[M], v[M];
struct node{
int x, k, id;
}q[Q];
int beg[N], siz[N], son[N], t[N<<2], ans[Q];
template<class T>inline void read(T &x){
x = 0; bool fl = 0; char c = getchar();
while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
if (fl) x = -x;
}
void _add(int x, int y){
nxt[++ecnt] = h[x]; v[ecnt] = y;
h[x] = ecnt;
}
bool cmp(node x, node y){
return x.x < y.x;
}
void dfs1(int u, int fa){
siz[u] = 1;
for (int i = h[u]; i; i = nxt[i])
if (v[i] != fa){
dfs1(v[i], u);
siz[u] += siz[v[i]];
if (siz[v[i]] > siz[son[u]]) son[u] = v[i];
}
}
#define ls (u<<1)
#define rs (u<<1^1)
#define mid (l+r>>1)
void push_up(int u){
t[u] = t[ls]+t[rs];
}
void modify(int u, int l, int r, int P, int X){
if (l == r){
t[u] += X;
return;
}
if (P <= mid) modify(ls, l, mid, P, X);
else modify(rs, mid+1, r, P, X);
push_up(u);
}
int query(int u, int l, int r, int K){
if (l == r) return l;
if (t[ls] >= K) return query(ls, l, mid, K);
else return query(rs, mid+1, r, K-t[ls]);
}
void add(int u, int fa, int X){
modify(1, 1, vn, c[u], X);
for (int i = h[u]; i; i = nxt[i])
if (v[i] != fa)
add(v[i], u, X);
}
void dfs(int u, int fa, bool opt){
for (int i = h[u]; i; i = nxt[i])
if (v[i] != fa && v[i] != son[u])
dfs(v[i], u, 1);
if (son[u]) dfs(son[u], u, 0);
for (int i = h[u]; i; i = nxt[i])
if (v[i] != fa && v[i] != son[u])
add(v[i], u, 1);
modify(1, 1, vn, c[u], 1);
if (beg[u])
for (int i = beg[u]; i <= m && q[i].x == u; ++ i)
ans[q[i].id] = vec[query(1, 1, vn, q[i].k)-1];
if (opt) add(u, fa, -1);
}
int main()
{
freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
read(n);
for (int i = 1; i <= n; ++ i){
read(c[i]);
vec.push_back(c[i]);
}
sort(vec.begin(), vec.end());
vec.resize(unique(vec.begin(), vec.end())-vec.begin());
vn = vec.size();
for (int i = 1; i <= n; ++ i)
c[i] = lower_bound(vec.begin(), vec.end(), c[i])-vec.begin()+1;
ecnt = 1;
for (int i = 1; i < n; ++ i){
int x, y;
read(x); read(y);
_add(x, y); _add(y, x);
}
read(m);
for (int i = 1; i <= m; ++ i){
read(q[i].x); read(q[i].k); q[i].id = i;
}
sort(q + 1, q + m + 1, cmp);
for (int i = m; i >= 1; -- i)
beg[q[i].x] = i;
dfs1(1, 0);
dfs(1, 0, 0);
for (int i = 1; i <= m; ++ i)
printf("%d\n", ans[i]);
return 0;
}
T2
题意
这道题分三个部分:
-
n 个变量,每个的值都是在 [x,y] 内等概率随机的整数。求这 n 个变量的最大值的期望。
n ≤ 1 0 9 , x , y ≤ 1 0 5 n\le 10^9,x,y\le 10^5 n≤109,x,y≤105 -
n 个变量,每个的值都是在 [x,y] 内等概率随机的整数。求这 n 个变量的最大值的期望。
n ≤ 1000 , x , y ≤ 1 0 18 n\le 1000,x,y\le 10^{18} n≤1000,x,y≤1018 -
n 个变量,每个的值都是在 [x,y] 内等概率随机的实数。求这 n 个变量的最大值的期望。
n , x , y ≤ 1 0 18 n,x,y\le 10^{18} n,x,y≤1018
思路
第一个题很好推式子。首先我们可以知道最大值为 i i i 的概率是 i n − ( i − 1 ) n / ( y − x + 1 ) n i^n-(i-1)^n/(y-x+1)^n in−(i−1)n/(y−x+1)n ,算是一个容斥。概率乘上权值就是期望,乘完之后大部分都消掉了,留下了这个式子:
a n s = y − ∑ i = 1 y − x i n ( y − x + 1 ) n ans=y-\frac{\sum_{i=1}^{y-x}i^n}{(y-x+1)^n} ans=y−(y−x+1)n∑i=1y−xin
第一题就拿着这个式子暴力算就好了。
然后是第二题,第二题的 n n n 比较小。考虑如何让复杂度与 x , y x,y x,y 无关。我们有
( p + 1 ) k + 1 − 1 = ∑ i = 0 k ∑ j = 1 p C k + 1 i ∗ j i (p+1)^{k+1}-1=\sum_{i=0}^{k}\sum_{j=1}^{p}C_{k+1}^{i}*j^i (p+1)k+1−1=i=0∑kj=1∑pCk+1i∗ji
换一下顺序
( p + 1 ) k + 1 − 1 = ∑ i = 0 k C k + 1 i ∑ j = 1 p ∗ j i (p+1)^{k+1}-1=\sum_{i=0}^{k}C_{k+1}^{i}\sum_{j=1}^{p}*j^i (p+1)k+1−1=i=0∑kCk+1ij=1∑p∗ji
发现可以用这个式子 O ( n 2 ) O(n^2) O(n2) 递推 n n n 次方前缀和。那么第二题就做完了。
第三题具体推导用微积分,出题人小学三年级就学过微积分了,但是我高二还一无所知,所以感性理解一下,问题就是在一个长度为 1 的线段上切 n 刀求最右边的那段的期望长度。
这个可以看做在一个圆上切 n+1 刀,取出其中一段的期望。显然每段的期望长度是一样的,所以就是 1 n + 1 \frac{1}{n+1} n+11 。
前两个问题推式子还是挺好推的。但是我只写出了第一个而没有看到 n 小于等于 1000 去想第二个式子。也是对这种 n 次方前缀和的处理方法的不熟悉。而且第二个题还可以用第二类斯特林数做,也是
O
(
n
2
)
O(n^2)
O(n2) 的。
第三个结论背下来就好了。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
int typ;
LL n, x, y;
template<class T>inline void read(T &x){
x = 0; bool fl = 0; char c = getchar();
while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
if (fl) x = -x;
}
int add(int &x, int y){x += y; if (x >= mod) x -= mod;}
int fpow(LL x, LL y){
x %= mod;
int ret = 1;
while (y){
if (y & 1) ret = 1LL*ret*x%mod;
x = 1LL*x*x%mod;
y >>= 1;
}
return ret;
}
int inv(LL x){
return fpow(x, mod-2);
}
namespace Solver1
{
void main(){
int ans = 0;
for (int i = 1; i <= y-x; ++ i)
add(ans,fpow(i, n));
ans = 1LL*ans*inv(fpow(y-x+1, n))%mod;
ans = (y-ans+mod)%mod;
printf("%d\n", ans);
exit(0);
}
}
namespace Solver2
{
const int N = 1000 + 10;
int fac[N], ifac[N], sum[N], ans;
void init(){
fac[0] = 1;
for (int i = 1; i < N; ++ i)
fac[i] = 1LL*fac[i-1]*i%mod;
ifac[N-1] = inv(fac[N-1]);
for (int i = N-2; i >= 0; -- i)
ifac[i] = 1LL*ifac[i+1]*(i+1)%mod;
}
int C(int x, int y){
return 1LL*fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}
void main(){
init();
sum[0] = (y-x)%mod;
for (int k = 1; k <= n; ++ k){
sum[k] = fpow((y-x+1)%mod, k+1)-1;
for (int i = 0; i < k; ++ i)
add(sum[k], mod-1LL*C(k+1, i)*sum[i]%mod);
sum[k] = 1LL*sum[k]*inv(k+1)%mod;
}
ans = (y-1LL*sum[n]*inv(fpow((y-x+1)%mod, n))%mod+mod)%mod;
printf("%d\n", ans);
exit(0);
}
}
namespace Solver3
{
void main(){
int ans = (y-1LL*(y-x)%mod*inv(n+1)%mod+mod)%mod;
printf("%d\n", ans);
}
}
int main()
{
freopen("freshmen.in", "r", stdin); freopen("freshmen.out", "w", stdout);
read(typ); read(n); read(x); read(y);
if (typ == 1 && y-x <= 1e5) Solver1::main();
else if (typ == 1 && n <= 1000) Solver2::main();
else if (typ == 2) Solver3::main();
return 0;
}
T3
题意
有一个序列 { a i } \{a_i\} {ai},每个数 a i ∈ { 1 , 2 , 3 } a_i\in\{1,2,3\} ai∈{1,2,3} 。有以下两种操作:
- 将一个区间 $[l,r] $内的所有数修改为 x x x
- 在区间 [ l , r ] [l,r] [l,r] 内选若干个位置 { p 1 . . . p m } \{p_1...p_m\} {p1...pm},满足 ∀ i < m , p i < p i + 1 , a p i ≤ a p i + 1 \forall i<m,p_i<p_{i+1},a_{p_i}\le a_{p_{i+1}} ∀i<m,pi<pi+1,api≤api+1 ,然后用恰好 k k k 种颜色的旗子(每种颜色可以用任意多个)把这些位置插满。询问这样的插旗方案数。
对于询问, k ≤ 5 k\le 5 k≤5 。
思路
题目说这是一道送分题。出题人倒是良心,数据里 70 分都是暴力分。然而却没送到我手里,我题意看挂了,然后又没看到第二个样例(雾)。话说第二个样例就在 pdf 的第二页,看数据范围的时候肯定是要看到的呀 qwq
首先可以想出暴力 DP 。 f i , j , k f_{i,j,k} fi,j,k 表示前 i i i 个位置,已经插了 j j j 种颜色的旗子,结尾的 a i = k a_i=k ai=k 的方案数。
然后感觉可以用矩阵优化,就像动态 DP 那样。但是复杂度不是很对劲。
考虑换一个方式 DP 。
假如有一个方案,插了 s s s 个旗子,那么他的贡献为 ∑ i = 1 k ( − 1 ) k − i ⋅ C k i ⋅ i s \sum_{i=1}^{k}(-1)^{k-i}\cdot C_{k}^{i}\cdot i^s ∑i=1k(−1)k−i⋅Cki⋅is 。
那么所有方案的答案就是
∑ s ∈ S ∑ i = 1 k ( − 1 ) k − i ⋅ C k i ⋅ i s \sum_{s\in S}\sum_{i=1}^{k}(-1)^{k-i}\cdot C_{k}^{i}\cdot i^s s∈S∑i=1∑k(−1)k−i⋅Cki⋅is
换个顺序
∑ i = 1 k ( − 1 ) k − i ⋅ C k i ⋅ ∑ s ∈ S i s \sum_{i=1}^{k}(-1)^{k-i}\cdot C_{k}^{i}\cdot \sum_{s\in S}i^s i=1∑k(−1)k−i⋅Cki⋅s∈S∑is
然后我们要做的就是求出所有的 i s i^s is 。因为 i i i 的范围很小,所以仍旧考虑 DP 。设 f i , s , t , l , h f_{i,s,t,l,h} fi,s,t,l,h 表示在 [ s , t ] [s,t] [s,t] 区间内,最左边的 a i = l a_i=l ai=l ,最右边的 a i = h a_i=h ai=h 的所有方案的 ∑ i s \sum i^s ∑is 。然后就很方便转移了,用线段树维护一下就好了。
这种高妙的做法我倒是想不出来,但是暴力的 70 分没有拿到实在是不应该,以后考试不能睡觉了。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e4 + 10, mod = 1e9 + 7, K = 6, H = 4;
int n, q, a[N], c[K][K], sum[K][N];
template<class T>inline void read(T &x){
x = 0; bool fl = 0; char c = getchar();
while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
if (fl) x = -x;
}
void add(int &x, int y){x += y; if (x >= mod) x-= mod;}
int pls(int x, int y){x += y; return (x >= mod ? x-mod : x);}
void init(){
for (int i = 1; i <= 5; ++ i){
sum[i][1] = i;
for (int j = 2; j < N; ++ j)
sum[i][j] = (1LL*sum[i][j-1]*(i+1)+i)%mod;
}
c[0][0] = 1;
for (int i = 1; i <= 5; ++ i){
c[i][0] = 1;
for (int j = 1; j <= i; ++ j)
c[i][j] = c[i-1][j-1]+c[i-1][j];
}
}
namespace SegT
{
#define ls (u<<1)
#define rs (u<<1^1)
#define mid (l+r>>1)
struct node{
int f[K][H][H];
node(){}
node(int c, int len){
memset(f, 0, sizeof f);
for (int i = 1; i <= 5; ++ i)
f[i][c][c] = sum[i][len];
}
node operator + (node u){
node ret;
for (int i = 1; i <= 5; ++ i)
for (int j = 1; j <= 3; ++ j)
for (int k = j; k <= 3; ++ k)
ret.f[i][j][k] = pls(f[i][j][k], u.f[i][j][k]);
for (int i = 1; i <= 5; ++ i)
for (int j = 1; j <= 3; ++ j)
for (int k = j; k <= 3; ++ k)
for (int p = j; p <= k; ++ p)
for (int w = p; w <= k; ++ w)
add(ret.f[i][j][k], 1LL*f[i][j][p]*u.f[i][w][k]%mod);
return ret;
}
}t[N<<2];
int laz[N<<2];
void push_up(int u){
t[u] = t[ls]+t[rs];
}
void push_down(int u, int ln, int rn){
if (laz[u]){
t[ls] = node(laz[u], ln);
laz[ls] = laz[u];
t[rs] = node(laz[u], rn);
laz[rs] = laz[u];
laz[u] = 0;
}
}
void build(int u, int l, int r){
if (l == r){
t[u] = node(a[l], 1);
return;
}
build(ls, l, mid);
build(rs, mid+1, r);
push_up(u);
}
void modify(int u, int l, int r, int L, int R, int C){
if (L <= l && r <= R){
t[u] = node(C, r-l+1);
laz[u] = C;
return;
}
push_down(u, mid-l+1, r-mid);
if (L <= mid) modify(ls, l, mid, L, R, C);
if (mid < R) modify(rs, mid+1, r, L, R, C);
push_up(u);
}
node query(int u, int l, int r, int L, int R){
if (L <= l && r <= R) return t[u];
push_down(u, mid-l+1, r-mid);
if (mid < L) return query(rs, mid+1, r, L, R);
else if (R <= mid) return query(ls, l, mid, L, R);
else return query(ls, l, mid, L, R)+query(rs, mid+1, r, L, R);
}
}
using namespace SegT;
int main()
{
freopen("sendpoints.in", "r", stdin); freopen("sendpoints.out", "w", stdout);
read(n); read(q);
for (int i = 1; i <= n; ++ i) read(a[i]);
init();
build(1, 1, n);
for (; q--; ){
int opt, l, r, x;
read(opt); read(l); read(r); read(x);
if (opt == 1) modify(1, 1, n, l, r, x);
else{
node nod = query(1, 1, n, l, r);
int ans = 0;
for (int i = x, t = 1; i >= 1; -- i, t ^= 1){
int tmp = 0;
for (int j = 1; j <= 3; ++ j)
for (int k = j; k <= 3; ++ k)
add(tmp, nod.f[i][j][k]);
if (t) add(ans, 1LL*c[x][i]*tmp%mod);
else add(ans, mod-1LL*c[x][i]*tmp%mod);
}
printf("%d\n", ans);
}
}
return 0;
}
后记
感觉总体难度不大的一套题,但是我却一题都没有 AC 。主要原因还是在态度上。
只剩最后几天了,对每一道题都需要非常认真,把能拿的分全都拿到。
还有,晚上早点睡觉,中午也早点午睡,早上做题不要犯困。