T1:中间值(median)
【题目背景】
Maxtir 喜欢序列的中间值。
【题目描述】
现在 Maxtir 有两个长度为 的非严格单调递增序列 。 他想知道这两个序列中的某两个连续间合并后形成的新序列的中间值,这里我们保证这两个区间长度之和为奇数。他觉得一直求相同序列的中间值太无聊了,于是他决定改变序列中的一些数,当然,他并不会让这个序列失去非严格单调递增这个美丽的性质。
【输入格式】
第一行输入两个正整数 ,其中 是操作和询问次数。接下来两行每行输入 个非负整数, 每一行分别表示两个序列的初始值。接下来 行,每行输入一个操作或询问,以(1 x y z)或(2 l_1 r_1 l_2 r_2)的形式给出。对于 1 操作,保证 (xin [0,1])若(x=0) 将(a_y)修改成z,否则将(b_y)修改成z,保证修改前后序列都是非严格单调增的。对于 2 操作,输出a序列的([l_1,r_1])区间与b序列的([l_2,r_2])区间合并后形成的新区间的中间值,数据保证两个区间长度之和为奇数。
【输出格式】
对于每个询问,输出一行一个整数 表示答案。
【样例输入】
5 5
12 41 46 68 69
35 61 82 84 96
2 1 4 3 5
1 0 5 75
2 2 4 3 4
2 3 4 1 5
2 1 4 2 4
【样例输出】
68
68
68
61
另有两个样例,见下发文件。
【数据范围】
对于30%的数据,满足(nleq 1000),(mleq 2000)
对于70%的数据,满足(nleq 10^5),(mleq 10^6)
对于100%的数据,满足(n<=5ast 10^5),(m<=10^6)
(0leq a[i],b[i],zleq 10^9,1leq l_1leq r_1leq n,0leq l_2leq r_2leq n)
由于本题输入数据量较大, 使用 scanf读入数据也需要花费较多时间,建议采用下面的代码来读入一个非负整数。
int ri() {
char c = getchar(); int x = 0; for(;c < '0' || c > '9'; c = getchar());
for(;c >= '0' && c <= '9'; c = getchar()) x = x * 10 - '0' + c; return x;
}
出题人Solution:
按照 NOIP 的惯例,此题是一道送分题。
不难想到二分答案。只需验证某个数是否合法。可以采用
lower_bound,upper_bound 做到 (O(nlog^2n)) ,我佛系卡了一波 70。至于怎么卡的可以参见 data-max-Baoli70 程序。
实际上我们可以进行一波分类讨论。可以通过当前数的位置得到这个数在另一个序列的期望位置。假设当前的数为 x ,期望位置的数为 y ,下一个数为 z ,那么 (zleq xleq y) 时 x 就是答案,否则比较一下大小,往两边跳。
这种方法要特判很多种情况。事实上,我们还有一种较为简便,普适性更强的方法。假设当前要取的是区间的第 k 大,将 k 折半,放在两个区间的对应位置 t s, 上,比较(a[s]),b[t](不妨设)a[s]<b[t]$ ,那么答
案可以化归至区间 ([l_1,s-1],[l_2,r_2])的第(frac {k} {2})大数(因为$ a (序列比)a[s]$小的 那 些 数 一 定 可 以 全 部 舍 去 ), 递 归 即 可 , 可 以 参 见
data_max_median2,鸣谢 xxcc 提供代码。
事实上,这两种方法都可以拓展到任意区间长度的第 (k)大问题上,
做法一样,只是为了标题的统一性(出题人比较懒),所以出了中间值。
Code:
#include<cstdio>
#include<algorithm>
const int N = 5e5 + 10;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int a[N], b[N];
int Cal(int l, int r, int *a, int st, int ed, int *b) {
int p = r - l + 1 + ed - st + 1 >> 1, L = l, R = r;
for(;L <= R;) {
int m = L + R >> 1, c = p - (m - l + 1) + st;
if(c == st - 1 && m - l == p && a[m] <= b[st]) return a[m];
if(c < st) {R = m - 1; continue;}
else if(c > ed) {L = m + 1; continue;}
if(a[m] >= b[c] && (a[m] <= b[c + 1] || c == ed)) return a[m];
if(a[m] >= b[c]) R = m - 1;
else L = m + 1;
}
return 0;
}
int main() {
freopen("median.in","r",stdin);
freopen("median.out","w",stdout);
int n = ri(), m = ri();
for(int i = 1;i <= n; ++i) a[i] = ri();
for(int i = 1;i <= n; ++i) b[i] = ri();
for(;m--;) {
int op = ri();
if(op == 1) {
int w = ri(), x = ri(), y = ri();
!w ? a[x] = y : b[x] = y;
}
else {
int l = ri(), r = ri(), l1 = ri(), r1 = ri(), x;
if(x = Cal(l, r, a, l1, r1, b)) printf("%d
", x);
else printf("%d
", Cal(l1, r1, b, l, r, a));
}
}
return 0;
}
/*
6 1
1 2 4 5 6 7
1 1 3 5 6 7
2 1 6 1 1
*/
T2:最小值(min)
【题目背景】
Maxtir 更喜欢序列的最小值。
【题目描述】
Maxtir 又有一个长度为 的整数序列 。
定义区间([l,r]) 的价值为(f(min_{i=l}^{r}a_i)),(f(x)=Ax^3+Bx^2+Cx+D)
特殊地,规定(min_{i=x}^{x}a_i=a_x)现在 Maxtir 希望将序列分割成若干个区间,使得所有区间的价
值之和最大。
【输入格式】
第一行输入一个正整数(n)和四个整数(A,B,C,D) 。
第二行输入(n)个整数,第(i)个数表示(a_i)。
【输出格式】
输出一行一个整数(ans)表示答案。
【样例输入】
5 0 0 1 10
9 9 5 2 6
【样例输出】
81
另有两个样例,见下发文件。
【数据范围】
对于10%的数据,满足(nleq 100)
对于30%的数据,满足(nleq 1000)
对于另外20%的数据,满足(A=B=0,Cleq 0)
对于100%的数据,满足(nleq 2ast10^5),(forall | f(a_i)|leq 10^{13}),输入数据均在整数(int)范围内
Solution:
对于30%的数据,可以想到一个DP方程:
(dp[i]=max_{j=0}^{i-1}dp[j]+f(min_{x=j+1}^{i}a_x),dp[0]=0)
其中(dp[i])表示分割([1,i])的最大答案
显然可以采用一个单调递增的栈来维护(g_x=min_{x=j}^ia_x),具体的单调栈中的元素(l_1,l_2...l_m)表示(g_{l_i}
eq g_{l_i}-1)的每个(l_i)(就是最小值变化的转折点),那么有(forall xin [l_i,l_{i+1}-1],g_x)相同,此时(dp)值最大的那一个点一定最优秀,于是维护(h_i=max_{x=l_i}^{l_i+1}dp[x]),表示每个取到最小值元素对应的区间的最优答案。
这样的话,每一次的答案就是(maxh_i+f(g_{l_i})),采用一棵线段树或者可删除堆维护单调栈即可。
Code:
#include<cstdio>
#include<cstring>
#include<algorithm>
const int Nt = 524287; const long long inf = 0x3f3f3f3f3f3f3f3f;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int a[Nt], st[Nt], tp, n, A, B, C, D;
long long T[Nt << 1], f[Nt], mx[Nt];
void Up(int i, long long x)
{for(T[i += Nt] = x; i >>= 1;) T[i] = std::max(T[i << 1], T[i << 1 | 1]);}
long long Cal(long long x) {return ((A * x + B) * x + C) * x + D;}
int main() {
freopen("min.in","r",stdin);
freopen("min.out","w",stdout);
n = ri(); A = ri(); B = ri(); C = ri(); D = ri();
for(int i = 1;i <= n; ++i) a[i] = ri();
std::memset(T, -0x3f, sizeof(T));
f[0] = 0; mx[1] = 0; st[tp = 1] = a[1]; Up(1, Cal(a[1]));
for(int i = 1;i <= n; ++i) {
f[i] = T[1]; long long x = f[i];
for(;st[tp] > a[i + 1] && tp;) x = std::max(x, mx[tp]), Up(tp--, -inf);
st[++tp] = a[i + 1]; mx[tp] = x; Up(tp, x + Cal(st[tp]));
}
printf("%lld
", f[n]);
return 0;
}
T3:最大值(max)
【题目背景】
Maxtir 最喜欢最大值。
【题目描述】
Maxtir 有 个横向摆放的魔法阵,启动一个魔法阵要使用魔法晶
石。
一个魔法晶石有三种属性,((x_i,y_i,p_i)),表示他有(p_i)的概率在第(x_i)个魔法阵上出现,其能量为(y_i)。对于任意的魔法石都有(0leq y_i leq k),(k)为给定的常数。
任意一个时刻,某个魔法阵的能量(v_i)为在这个魔法阵上的所有魔法晶石的最小值,特殊地,规定没有魔法晶石的魔法阵能量为 0。
现在 Maxtir 有(m)个魔法晶石,以及(q)次启动魔法阵的机会,第 (i)次,他可以从第(l_i)到第(r_i)个魔法阵吸取能量,其吸取的能量为所有魔
法阵的最大能量,即(A_i=max_{j-l_i}^{r_i}) (同上一题类似地规定(max_{i=x}^xv_i=v_x))。
同时,保证([l_i,r_i])互不包含,否则会出现不可描述的问题。
现在, 他希望知道他(q)次吸取的能量的期望值之和, 在(mod10^9+7)意义下告诉他答案即可
【输入格式】
第1行输入四个正整数n, m, q。
第2至n+1行中,第i+1行输入魔法晶石i的三种属性((x_i,y_i,p_i))。
接下来q行,每行两个正整数(l_i,r_i),数据保证([l_i,r_i])互不包含。
【输出格式】
输出一行一个正整数ans表示答案。
【样例输入】
3 3 2
1 1 500000004
2 2 333333336
3 3 1
1 2
2 3
【样例输出】
4
【样例解释】
(500000004equiv frac {1} {2}mod(10^9+7))
(333333333equiv mod(10^9+7))
最终的魔法阵中的晶石序列可能是
((igotimes,igotimes,3),(igotimes,2,3),(1,igotimes,3),(1,2,3))四种,他们的概率分别是(frac{1} {3}),(frac{1}{6}),(frac{1}{3}),(frac{1}{6}),两次吸取的能量分别是((0,3),(2,3),(1,3),(2,3)),最终的答案是(3 imesfrac{1}{3}+5 imesfrac{1}{6}+4 imesfrac{1}{3}+5 imesfrac{1}{6}=4)另有两个样例,见下发文件。
【数据范围】
对于100%的数据,满足(0leq y_ileq 10^9,1leq q leq n,0leq p_i<10^9+7)
测试点编号 | 数据范围 | 特殊性质 |
---|---|---|
1 | (nleq 20,mleq 20) | |
2,3 | (nleq 100,mleq 500) | |
4 | (nleq1000,mleq 5000) | |
5 | (nleq10^5,mleq 2 imes10^5) | (q=n) |
6 | (mleq 10^5, mleq 2 imes10^5) | (q=n-1) |
7,8,9,10 | (nleq10^5,mleq2 imes 10^5) |
Solution:
题目大意: n 个位置, 每个位置上有若干个有一定概率出现的数,给定若干个询问区间, 每次询问区间内每个位置上所有数最小值的最大值的期望之和。
对于 10% 的数据,暴力枚举每个数出现或不出现即可。复杂度(O(nq2^m))
对 于 30% 的 数 据 , 考 虑 一 个 随 机 变 量 x , 其 期 望 值 为(E(x)=Sigma_{i=i}^infty p(x geq i))(整数概率公式) ,实际就是看到最大值的最小值就二分答案。
于是我们可以枚举 x ,单独考虑每个询问。
即求(P(max_{i=l}^{r}v_igeq x))
(Rightarrow) (1-P(max_{i=l}^rv_ileq x-1))
(Rightarrow) (1-P(forall iin [l,r]v_ileq x-1))
(Rightarrow) (1-prod_{i=l}^rP(v_ileq x-1))
(Rightarrow) (1-prod_{i=l}^r1-P(v_igeq x))
于是我们只需要求出每个位置的最小值大于等于 x 的概率即可。
这等价于所有小于 x 的数都不出现且至少出现一个大于等于 x 的数。
所以我们可以把小于 x 的数不选的概率乘起来再减去所有数都不选的概率,就是我们所要的 )(P(v_igeq x))
同时发现,只有当 x 取到所给数的某个值的时候,概率才会有所变化,其余情况概率不变,可以直接加起来。这样可以顺利拿到30 分。
对于 40% 的数据, 考虑某次更改的贡献, ) (1-P(v_igeq y)
ightarrow 1-P(v_igeq x))事实上对于某个询问,外面的 1-可以最后统计,仅仅考虑(q=prod_{i=l}^r1-P(v_igeq x))的变化。我们可以简单地将(q
ightarrow q imes frac {1-P(v_i geq x)} {1-P(v_igeq y)})这样可做到(O(logn)) 修改某个询问的答案。
特殊地, 我们发现(1-P(v_igeq y))可能等于零, 但是同时也发现 (P(v_igeq x))随着 x 的变大而递减,于是有(1-P(v_igeq x)=0Rightarrow1-P(v_igeq y)=0(yleq x))于是我们从大到小枚举权值即可解决这个问题。这样子的复杂度是(O(mq)),可以得到 40 分。
到目前,还有一个条件没用,就是区间互不包含。
对于数据点 5,6,发现一个点仅仅会被 1 个或 2 个区间包含,用邻接表处理出每个点对应要处理的区间即可拿到 60 分。
对于 100% 的数据,按区间左端点排(出题人比较懒并没有打乱顺序) ,可以得到每个点被包含的区间也是一个区间。这样可以实现点和区间的转化。于是问题被转化为将一个区间乘上某一个数。采用
线段树打标记维护,每次加上全局的答案即可,复杂度(O(mlogq)) ,可
以通过全部数据。
Code:
#include<cstdio>
#include<algorithm>
#define ls p << 1
#define rs p << 1 | 1
#define rt 1, 1, Q
const int N = 1e5 + 10, Y = 2e5 + 10, P = 1e9 + 7;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int t[N << 2], tm[N << 2], c[Y], b[Y], l[Y], r[Y], pr[Y], to[Y << 1], nx[Y << 1], Q, n, m, k, H;
void add(int x, int p) {to[++H] = 1LL * to[pr[x]] * (1 - p) % P; nx[H] = pr[x]; pr[x] = H;}
struct D {int l, r;} q[N];
struct X {int x, y, p;}p[Y];
bool cmp1(X a, X b) {return a.y < b.y;}
bool cmp2(D a, D b) {return a.l == b.l ? a.r < b.r : a.l < b.l;}
int Pow(int x, int k) {
int r = 1;
for(;k;x = 1LL * x * x % P, k >>= 1) if(k & 1) r = 1LL * r * x % P;
return r;
}
void B(int p, int L, int R) {
tm[p] = 1; if(L == R) return void(t[p] = 1);
int m = L + R >> 1; B(ls, L, m); B(rs, m + 1, R);
t[p] = t[ls] + t[rs];
}
void Tag(int p, int v) {tm[p] = 1LL * tm[p] * v % P; t[p] = 1LL * t[p] * v % P;}
void Pd(int p) {if(tm[p] != 1) Tag(ls, tm[p]), Tag(rs, tm[p]), tm[p] = 1;}
void M(int p, int L, int R, int st, int ed, int v) {
if(L == st && ed == R) return Tag(p, v);
int m = L + R >> 1; Pd(p);
if(st <= m) M(ls, L, m, st, std::min(ed, m), v);
if(ed > m) M(rs, m + 1, R, std::max(st, m + 1), ed, v);
t[p] = (t[ls] + t[rs]) % P;
}
void C(int x) {
if(l[x] > r[x]) return ;
int m = Pow(1 - (to[pr[x]] - b[x]) % P, P - 2); pr[x] = nx[pr[x]];
M(rt, l[x], r[x], 1LL * (1 - (to[pr[x]] - b[x]) % P) * m % P);
}
int main() {
freopen("max.in","r",stdin);
freopen("max.out","w",stdout);
n = ri(); m = ri(); Q = ri(); int tp = 0;
for(int i = 1;i <= m; ++i) {
int x = ri(), y = ri(), px = ri();
if(!px || !y) continue;
p[++tp].x = x; p[tp].y = y; p[tp].p = px;
}
std::sort(p + 1, p + tp + 1, cmp1);
for(int i = 1;i <= n; ++i) to[++H] = 1, pr[i] = H;
for(int i = 1;i <= tp; ++i) add(p[i].x, p[i].p);
for(int i = 1;i <= n; ++i) b[i] = to[pr[i]];
for(int i = 1;i <= Q; ++i) q[i].l = ri(), q[i].r = ri();
std::sort(q + 1, q + Q + 1, cmp2);
int L = 1, R = 0;
for(int i = 1;i <= n; ++i) {
for(;L <= R && q[L].r < i; ++L) ;
for(;q[R + 1].l <= i && R < Q; ++R) ;
l[i] = L; r[i] = R;
}
B(rt); int A = 0; p[0].y = 0;
for(int i = tp, j; i; i = j) {
for(j = i;p[j].y == p[i].y && j; --j) C(p[j].x);
A = (A + 1LL * t[1] * (p[i].y - p[j].y)) % P;
}
A = (1LL * p[tp].y * Q - A) % P;
printf("%d
", (A + P) % P);
return 0;
}
谢谢收看,祝身体健康!
话说MarkDown就是好康,就是我写的太慢了......