2024.7 - 做题记录与方法总结-Part1

2024/07/01

AtCoder Beginner Contest 360

E - Random Swaps of Balls

期望 d p dp dp

问题陈述

N − 1 N - 1 N1 个白球和一个黑球。这些 N N N 个球排成一排,黑球最初位于最左边的位置。

高桥正好要进行下面的操作 K K K 次。

  • 1 1 1 N N N 之间均匀随机地选择一个整数,包括两次。设 a a a b b b 为所选整数。如果是 a ≠ b a \neq b a=b ,把左边的 a a a -th 和 b b b -th 两个球交换。

经过 K K K 次操作后,让黑球位于左边的 x x x -th 位置。求 x x x 的期望值,模为 998244353 998244353 998244353

模数 998244353 998244353 998244353 的期望值是多少?
可以证明所求的期望值总是有理数。此外,在本题的限制条件下,还可以证明如果用不可约分数 P Q \frac{P}{Q} QP 表示这个值,那么就是 Q ≢ 0 ( m o d 998244353 ) Q \not \equiv 0 \pmod{998244353} Q0(mod998244353) 。因此,存在一个唯一的整数 R R R ,使得 R × Q ≡ P ( m o d 998244353 ) , 0 ≤ R ≤ 998244353 R \times Q \equiv P \pmod{998244353}, 0 \leq R \leq 998244353 R×QP(mod998244353),0R998244353 。报告这个 R R R .

参考了 Lanly 的题解

首先,我们可以看到:对于每一个不为 1 1 1 位置,其价值相等

那么,我们自然设置出状态:

  1. 其在 1 1 1 号位上
  2. 其不在 1 1 1 号位上

对于第 i i i 次操作,位于 1 1 1 号位的期望为 d p i , 0 dp_{i,0} dpi,0, 位于非 1 1 1 号位的期望为 d p i , 1 dp_{i,1} dpi,1

那么,黑球惨遭移动的概率为 $m = 2\frac{1}{n} \cdot \frac{n-1}{n} = \frac{2(n-1)}{n^2} $

故,黑球纹丝不动的概率为 s = 1 − m s = 1 - m s=1m

黑球移动到某一个位置的概率为 t o = m o v e n − 1 = 2 n 2 to = \frac{move}{n - 1} = \frac{2}{n^2} to=n1move=n22

转移为:

d p i , 0 = s ⋅ d p i − 1 , 0 + t o ⋅ d p i − 1 , 1 d p i , 1 = m ⋅ d p i − 1 , 0 + ( 1 − t o ) ⋅ d p i − 1 , 1 dp_{i,0} = s \cdot dp_{i-1,0} + to \cdot dp_{i-1,1} \\ dp_{i,1} = m \cdot dp_{i-1,0} + (1 - to) \cdot dp_{i-1,1} dpi,0=sdpi1,0+todpi1,1dpi,1=mdpi1,0+(1to)dpi1,1

最后,因为 p 1 = d p k , 0   p 2 = p 3 = p 4 = . . . = p n = d p k , 1 n − 1 p_1 = dp_{k,0} \ p_2 = p_3 = p_4 = ... = p_n = \frac{dp_{k,1}}{n-1} p1=dpk,0 p2=p3=p4=...=pn=n1dpk,1

由期望计算公式
E ( X ) = ∑ i = 1 n i p i E(X) = \sum_{i = 1}^n ip_i E(X)=i=1nipi

本题的答案
a n s = ∑ i = 1 n i p i = d p k , 0 + d p k , 1 n − 1 ∑ i = 2 n i = d p k , 0 + ( n + 2 ) ( n − 1 ) 2 ⋅ d p k , 1 n − 1 = ( n + 2 ) d p k , 1 2 + d p k , 0 ans = \sum_{i = 1}^n ip_i = dp_{k,0} + \frac{dp_{k,1}}{n-1}\sum_{i = 2}^{n} i = dp_{k,0} + \frac{(n + 2)(n-1)}{2} \cdot \frac{dp_{k,1}}{n-1} = \frac{(n + 2)dp_{k,1}}{2} + dp_{k,0} ans=i=1nipi=dpk,0+n1dpk,1i=2ni=dpk,0+2(n+2)(n1)n1dpk,1=2(n+2)dpk,1+dpk,0

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int mod = 998244353;

int qpow(int x,int k) {
	int res = 1;
	while(k) {
		if(k & 1) res = (res * x) % mod;
		x = (x * x) % mod;
		k >>= 1;
	}
	return res;
}

int inv(int x) {
	return qpow(x,mod - 2) % mod;
}

int dp[100005][2];

signed main() {

	int n = rd(),k = rd();

	if(n == 1) {
		wt(1);
		return 0;
	}

	dp[0][0] = 1;

	int P = inv(n);
	int P2 = (P * P) % mod;

	int move = ((n-1)<<1) % mod * P2 % mod;
	int stay = (1 - move + mod) % mod,to = (P2<<1) % mod;

	for(int i = 1;i<=k;i++) {
		dp[i][0] = (dp[i-1][0] * stay % mod + dp[i-1][1] * to % mod) % mod;
		dp[i][1] = (dp[i-1][0] * move % mod + dp[i-1][1] * ((1- to + mod) % mod) %mod) % mod;
	}
	
	int ans = dp[k][0] + ((n + 2) * (n - 1) / 2) % mod * dp[k][1] % mod * inv(n-1) % mod;

	wt(ans % mod);

	return 0;
}
G - Suitable Edit for LIS
问题陈述

给你一个长度为 N N N 的整数序列 A A A 。高桥将执行下列操作一次:

  • 1 1 1 N N N 之间选择一个整数 x x x ,以及一个任意整数 y y y 。将 A x A_x Ax 替换为 y y y

求操作后 A A A 的最长递增子序列(LIS)的最大可能长度。

什么是最长递增子序列?

序列 A A A 的子序列是从 A A A 中抽取一些元素而不改变顺序得到的序列。

序列 A A A 的最长递增子序列是严格递增的 A A A 的最长子序列。

看到题目,自然想到预处理出 p r e , s u f pre,suf pre,suf 用来记录 i i i 位前后,以 i i i 为 终点/起点 的 LIS,最后拼起来

前置:P1439 【模板】最长公共子序列

图方便就用 unordered_map <int,int> 来充当 树状数组

前后各遍历一次,就可求出 p r e , s u f pre,suf pre,suf 数组

剩下的参考 SJH_qwq 的 题解

AC-code:

#include<bits/stdc++.h>
using namespace std;

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int inf = 1e9+7;
struct BIT{
#define lowbit(x) (x &(-x))
unordered_map<int,int> c;

void add(int x,int d){
	while(x <= inf) {
		c[x] = max(c[x],d);
		x += lowbit(x);
	}
}

int query(int x){
	int res = 0;
	while(x) {
		res = max(res,c[x]);
		x -= lowbit(x);
	}
	return res;
}

}t;

signed main() {

	int n = rd();
	vector<int> a(n + 2),r(n + 2),l(n + 2);
	for(int i = 1;i<=n;i++) a[i] = rd();
	int ans = 0;
	for(int i = 1;i<=n;i++) {
		r[i] = t.query(a[i] - 1) + 1;
		t.add(a[i],r[i]);
		ans = max(ans,r[i] + (i != n));
	}
	t.c.clear();
	for(int i = n;i >= 1;i--) {
		l[i] = t.query(inf - a[i] - 1) + 1;
		t.add(inf - a[i],l[i]);
		ans = max(ans,l[i] + (i != 1));
	}
	t.c.clear();
	for(int i = 1;i<=n;i++) {
		int k = t.query(a[i + 1]);
		ans = max(ans,l[i + 1] + k + 1);
		t.add(a[i] + 2,r[i]);
	}

	wt(ans);


	return 0;
}

2024/07/08

P4587 [FJOI2016] 神秘数

题面:

题目描述

一个可重复数字集合 S S S 的神秘数定义为最小的不能被 S S S 的子集的和表示的正整数。例如 S = { 1 , 1 , 1 , 4 , 13 } S=\{1,1,1,4,13\} S={1,1,1,4,13},有: 1 = 1 1 = 1 1=1 2 = 1 + 1 2 = 1+1 2=1+1 3 = 1 + 1 + 1 3 = 1+1+1 3=1+1+1 4 = 4 4 = 4 4=4 5 = 4 + 1 5 = 4+1 5=4+1 6 = 4 + 1 + 1 6 = 4+1+1 6=4+1+1 7 = 4 + 1 + 1 + 1 7 = 4+1+1+1 7=4+1+1+1

8 8 8 无法表示为集合 S S S 的子集的和,故集合 S S S 的神秘数为 8 8 8

现给定长度为 n n n正整数序列 a a a m m m 次询问,每次询问包含两个参数 l , r l,r l,r,你需要求出由 a l , a l + 1 , ⋯   , a r a_l,a_{l+1},\cdots,a_r al,al+1,,ar 所组成的可重集合的神秘数。

输入格式

第一行一个整数 n n n,表示数字个数。

第二行 n n n 个正整数,从 1 1 1 编号。

第三行一个整数 m m m,表示询问个数。

输出格式

对于每个询问,输出一行对应的答案。

样例 #1
样例输入 #1
5
1 2 4 9 10
5
1 1
1 2
1 3
1 4
1 5
样例输出 #1
2
4
8
8
8
提示

对于 100 % 100\% 100% 的数据点, 1 ≤ n , m ≤ 10 5 1\le n,m\le {10}^5 1n,m105 ∑ a ≤ 10 9 \sum a\le {10}^9 a109

可持久线段树的典题

我们假设已经知道 可重集 { a ∣ a ∈ A [ l , r ] } \{a | a \in A[l,r]\} {aaA[l,r]} 中的数字可以组成的数的最小连续范围为 [ 0 , m a x n ] [0,maxn] [0,maxn]

我们向可重集里插入一个数 a r + 1 a_{r + 1} ar+1 ,观察有什么情况

如果 $a_{r + 1} \in [0,maxn + 1] $,因为可以通过给 a r + 1 a_{r + 1} ar+1 加上 [ 1 , m a x n ] [1,maxn] [1,maxn] 中的任意一个数 ,将可重集扩展到 [ 0 , m a x n + a r + 1 ] [0,maxn + a_{r + 1}] [0,maxn+ar+1]

如果 a r + 1 ∉ [ 0 , m a x n + 1 ] a_{r + 1}\not \in [0,maxn + 1] ar+1[0,maxn+1] ,在 m a x n + 1 maxn + 1 maxn+1 处必然有个缺口,致使答案为 m a x n + 1 maxn + 1 maxn+1

令缺口值为 a n s = m a x n + 1 ans = maxn + 1 ans=maxn+1

那么问题就简单了,我们从 [ 0 , 1 ] [0,1] [0,1] 逐步考究一段区间

对于 [ 0 , 1 ] [0,1] [0,1] 我们可以得到 [ l , r ] [l,r] [l,r] 中有多少个 1 1 1,累加到域中,得到 [ 0 , 1 ] → [ 0 , a n s 1 ] [0,1] \rightarrow [0,ans_1] [0,1][0,ans1]

然后,对于 [ 0 , a n s 1 ] [0,ans_1] [0,ans1],由于我们已经将 [ 0 , 1 ] [0,1] [0,1] 中的所有数字累加到域中了,那么我们自然从域 [ 2 , a n s 1 ] [2,ans_1] [2,ans1] 中累加数字,得到 [ 0 , a n s 1 ] → [ 0 , a n s 2 ] [0,ans_1]\rightarrow[0,ans_2] [0,ans1][0,ans2]

然后,对于 [ 0 , a n s 2 ] [0,ans_2] [0,ans2],由于我们已经将 [ 0 , a n s 1 ] [0,ans_1] [0,ans1] 的数字全部累加完了,我们只需要累加 [ a n s 1 + 1 ] [ans_1 + 1] [ans1+1] 中的数字,得到 [ 0 , a n s 2 ] → [ 0 , a n s 3 ] [0,ans_2] \rightarrow [0,ans_3] [0,ans2][0,ans3]

以此类推,就得到:

在添加完 [ 0 , r ] [0,r] [0,r] [ 0 , l − 1 ] [0,l-1] [0,l1] 两个时间戳内的数字

对于 [ 0 , l a s t a n s ]   ,  将  [ l , r ]  中 [ l a s t a n s + 1 , a n s ] 扩展到 [ 0 , a n s ] 中,使 [ 0 , a n s ] → [ 0 , n e w a n s ] 其中 [ 0 , l a s t a n s ] 是上一次已经累加过的区域, [ l a s t a n s + 1 , a n s ] 是询问时未累加的区域 , [ 0 , n e w a n s ] 是新的扩展域 对于 [0,lastans]\ ,\ 将\ [l,r]\ 中[lastans + 1,ans]扩展到 [0,ans] 中,使 [0,ans] \rightarrow [0,newans] \\ 其中 [0,lastans] 是上一次已经累加过的区域,[lastans + 1,ans]是询问时未累加的区域,[0,newans] 是新的扩展域 对于[0,lastans] ,  [l,r] [lastans+1,ans]扩展到[0,ans]中,使[0,ans][0,newans]其中[0,lastans]是上一次已经累加过的区域,[lastans+1,ans]是询问时未累加的区域,[0,newans]是新的扩展域

直到无法继续累加为止,输出 a n s ans ans,即可

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 1e5+5,inf = 1e9;

int rt[N<<2],cnt;

namespace sgt{
struct seg{
	int v,ls,rs;
}t[N<<8];

#define v(p) t[(p)].v
#define ls(p) t[(p)].ls
#define rs(p) t[(p)].rs
#define mid ((pl + pr) >> 1)

void push_up(int p) {
	v(p) = v(ls(p)) + v(rs(p));
}

void update(int r,int &p,int pl,int pr,int q,int V){
	if(!p) p = ++cnt;
	if(pl == pr) {
		v(p) += V;
		return;
	}
	if(q <= mid) {
		rs(p) = rs(r);
		ls(p) = ++cnt;
		t[ls(p)] = t[ls(r)];
		update(ls(r),ls(p),pl,mid,q,V); 
	}else {
		ls(p) = ls(r);
		rs(p) = ++cnt;
		t[rs(p)] = t[rs(r)];
		update(rs(r),rs(p),mid+1,pr,q,V);
	}
	push_up(p);
}

int query(int r,int p,int pl,int pr,int ql,int qr){
	if(ql <= pl && pr <= qr) return v(p) - v(r);
	int res = 0;
	if(ql <= mid) res += query(ls(r),ls(p),pl,mid,ql,qr);
	if(qr > mid) res += query(rs(r),rs(p),mid+1,pr,ql,qr);
	return res;
}

}

int n,m;

signed main() {
	n = rd();
	for(int i = 1;i<=n;i++) {
		int t = rd();
		sgt::update(rt[i-1],rt[i],1,inf,t,t);
	}
	m = rd();
	while(m--) {
		int ans = 1,l = rd(),r = rd(),lst = 0;
		while(1) {
			int res = sgt::query(rt[l - 1],rt[r],1,inf,lst + 1,ans);
			lst = ans;
			if(res) ans = res + ans;
			else break;
		}
		wt(ans),putchar('\n');
	}

	return 0;
}

P3380 树套树

有人没有写动态开点,我不说是谁

空间爆炸的代码
DEAD-code:

#include<bits/stdc++.h>
using namespace std;

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int M = 5e4+5;

int n,m,a[M];

struct FHQ{
int cnt = 0,root = 0;

struct node{
    int ls,rs;
    int key,pri;
    int size;
};

#define ls(p) t[p].ls
#define rs(p) t[p].rs
#define pri(p) t[p].pri


void newnode(int x) {
    cnt++;
    t[cnt].size = 1;
    t[cnt].ls = t[cnt].rs = 0;
    t[cnt].key = x;
    t[cnt].pri = rand();
}

void update(int u) {
  t[u].size = t[ls(u)].size + t[rs(u)].size + 1;
}

void spilt(int u,int x,int &L,int &R) {
  if(u == 0) {L = R = 0;return;}
  if(t[u].key <= x) {
    L = u;
    spilt(rs(u),x,rs(u),R);
  }else  {
    R = u;
    spilt(ls(u),x,L,ls(u));
  }
  update(u);
}

int merge(int L,int R) {
  if(L == 0 || R == 0 ) return L + R;
  if(pri(L) > pri(R)) {
    rs(L) = merge(rs(L),R);
    update(L);
    return L;
  }else {
    ls(R) = merge(L,ls(R));
    update(R);
    return R;
  }
}

void insert(int x) {
  int L,R;
  spilt(root,x,L,R);
  newnode(x);
  int aa = merge(L,cnt);
  root = merge(aa,R);
}

void del(int x){
  int L,R,p;
  spilt(root,x,L,R);
  spilt(L,x-1,L,p);
  p = merge(ls(p),rs(p));
  root = merge(merge(L,p),R);
}

int rank(int x){
    int L,R;
    spilt(root,x-1,L,R);
    int rv = t[L].size + 1;
    root = merge(L,R);
    return rv;
}

int kth(int u,int k) {
  if(k == t[ls(u)].size + 1) return u;
  if(k <= t[ls(u)].size) return kth(ls(u),k);
  if(k > t[ls(u)].size) return kth(rs(u),k - t[ls(u)].size - 1);
}

int pre(int x) {
    int L,R;
    spilt(root,x-1,L,R);
    int rv = t[kth(L,t[L].size)].key;
    root = merge(L,R);
    return rv;
}

int suc(int x) {
  int L,R;
  spilt(root,x,L,R);
  int rv = t[kth(R,1)].key;
  root = merge(L,R);
  return rv;
}

#undef ls
#undef rs
#undef mid
};

const int inf = 2147483647;

namespace sgt{
FHQ t[M<<2];

#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

void build(int p,int pl,int pr){
	for(int i = pl;i<=pr;i++) t[p].insert(a[pl]);
	t[p].insert(inf),t[p].insert(-inf);
	if(pl == pr) return;
	build(ls,pl,mid);
	build(rs,mid+1,pr);
}	

void update(int p,int pl,int pr,int k,int v){
	t[p].del(a[k]);
	t[p].insert(v);
	if(pl == pr) return;
	if(k <= mid) update(ls,pl,mid,k,v);
	else update(rs,mid+1,pr,k,v);
}

int getrank(int p,int pl,int pr,int l,int r,int k){
	if(l <= pl && pr <= r) return t[p].rank(k);
	if(r <= mid) {
		return getrank(ls,pl,mid,l,r,k);
	} else if(l > mid) {
		return getrank(rs,mid+1,pr,l,r,k);
	}else {
		int m = getrank(ls,pl,mid,l,r,k),n = getrank(rs,mid+1,pr,l,r,k);
		return m + n;
	}
}

int getkth(int pl,int pr,int k) {
	int l = 0,r = 1e8;
	while(l < r) {
		int Mid = (l + r + 1) / 2;
		if(getrank(1,1,n,pl,pr,Mid) < k)
        l = Mid;
    else r = Mid - 1;
	}
  return r;
}

int getpre(int p,int pl,int pr,int l,int r,int k){
  if(l <= pl && pr <= r) return t[p].pre(k);
  if(r <= mid) return getpre(ls,pl,mid,l,r,k);
  else if(l > mid) return getpre(rs,mid+1,pr,l,r,k);
  else {
    int m = getpre(ls,pl,mid,l,r,k),n = getpre(rs,mid+1,pr,l,r,k);
    return min(m,n);
  }
}

int getsuc(int p,int pl,int pr,int l,int r,int k) {
  if(l <= pl && pr <= r) return t[p].suc(k);
  if(r <= mid) return getsuc(ls,pl,mid,l,r,k);
  else if(l > mid) return getsuc(rs,mid+1,pr,l,r,k);
  else {
    int m = getsuc(ls,pl,mid,l,r,k),n = getsuc(rs,mid+1,pr,l,r,k);
    return max(m,n);
  }
}

#undef ls
#undef rs 
#undef mid
}


signed main() {

	n = rd(),m = rd();
	for(int i = 1;i<=n;i++) a[i] = rd();
  sgt::build(1,1,n);

  while(m--) {
    int opt = rd();
    int l = rd(),r = rd(),k = rd();
    switch (opt)
    {
    case 1:
      wt(sgt::getrank(1,1,n,l,r,k));
	  putchar('\n');
      break;
    case 2:
      wt(sgt::getkth(l,r,k));
	  putchar('\n');
      break;
    case 3:
      sgt::update(1,1,n,l,r);
      break;
    case 4:
      wt(sgt::getpre(1,1,n,l,r,k));
	  putchar('\n');
      break;
    case 5:
      wt(sgt::getsuc(1,1,n,l,r,k));
	  putchar('\n');
      break;
    }
  }

	return 0;
}

这下不得不练动态开点线段树了

template:

struct segment_tree
{
    int sum,lson,rson;
}tree[maxq<<2];
int tot;
void update(int &pos,int l,int r,int k)
{
    int mid=(l+r)>>1;
    if(!pos)
        pos=++tot;
   	if(l==r)
    {
        tree[pos].sum+=k;
        return;
    }
    if(x<=mid)
        update(tree[pos].lson,l,mid,k);
    if(y>mid)
        update(tree[pos].rson,mid+1,r,k);
    tree[pos].sum=tree[tree[pos].lson].sum+tree[tree[pos].rson].sum;
}
int main()
{
    int root=0;
    update(root,1,maxn,k);
    return 0;
}

树套树今天就先鸽着 (写了一下午)(难绷

动态开点线段树

P5459 [BJOI2016] 回转寿司

动态开点练手题

新手村的题目属于是

题面:

题目描述

酷爱日料的小Z经常光顾学校东门外的回转寿司店。在这里,一盘盘寿司通过传送带依次呈现在小Z眼前。

不同的寿司带给小Z的味觉感受是不一样的,我们定义小Z对每盘寿司都有一个满意度。

例如小Z酷爱三文鱼,他对一盘三文鱼寿司的满意度为 10 10 10;小Z觉得金枪鱼没有什么味道,他对一盘金枪鱼寿司的满意度只有 5 5 5;小Z最近看了电影《美人鱼》,被里面的八爪鱼恶心到了,所以他对一盘八爪鱼刺身的满意度是 − 100 -100 100

特别地,小Z是个著名的吃货,他吃回转寿司有一个习惯,我们称之为“狂吃不止”。具体地讲,当他吃掉传送带上的一盘寿司后,他会毫不犹豫地吃掉它后面的寿司,直到他不想再吃寿司了为止。

今天,小Z再次来到了这家回转寿司店, N N N 盘寿司将依次经过他的面前。其中,小Z对第 i i i 盘寿司的满意度为 a i a_i ai

小Z可以选择从哪盘寿司开始吃,也可以选择吃到哪盘寿司为止。他想知道共有多少种不同的选择,使得他的满意度之和不低于 L L L,且不高于 R R R

注意,虽然这是回转寿司,但是我们不认为这是一个环上的问题,而是一条线上的问题。即,小Z能吃到的是输入序列的一个连续子序列;最后一盘转走之后,第一盘并不会再出现一次。

输入格式

第一行三个正整数 N , L , R N,L,R N,L,R,表示寿司盘数,满意度的下限和上限。
第二行包含 N N N 个整数 a i a_i ai,表示小Z对寿司的满意度。

输出格式

一行一个整数,表示有多少种方案可以使得小Z的满意度之和不低于 L L L 且不高于 R R R

样例 #1
样例输入 #1
5 5 9
1 2 3 4 5
样例输出 #1
6
提示

【数据范围】

1 ≤ N ≤ 1 0 5 1\le N \le 10^5 1N105
∣ a i ∣ ≤ 1 0 5 |a_i| \le 10^5 ai105
0 ≤ L , R ≤ 1 0 9 0\le L,R \le 10^9 0L,R109

形式化的:
我们需要在序列中找出所有满足
对于 i < j , ∑ k = i j a k ∈ [ L , R ] 对于 i < j ,\sum_{k = i}^j a_k \in [L,R] 对于i<j,k=ijak[L,R]
的子序列 [ i , j ] [i,j] [i,j] 的个数

看到这题,自然会想到 逆序对统计、加入循环的思路

本题也是一样

既然题目中有 ∑ \sum ,那我们就先用前缀和先处理出来

然后,插入一个前缀和,找 k − R ∼ k − L k - R \sim k - L kRkL 中的个数

统计出来就结束了

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 1e5+5,inf = 1e18;

int n,l,r,a[N];

namespace sgt{
#define mid ((pl + pr) >> 1)
int t[N * 34 * 4 + 5],cnt,ls[N * 34 * 4 + 5],rs[N * 34 * 4 + 5];
int root = 0;
void push_up(int p) {
	t[p] = t[ls[p]] + t[rs[p]];
}

void update(int &p,int k,int d,int pl = -inf,int pr = inf) {
	if(!p) p = ++cnt;
	if(pl == pr) {t[p] += d;return;}
	if(k <= mid) update(ls[p],k,d,pl,mid);
	else update(rs[p],k,d,mid+1,pr);
	push_up(p);
}

int query(int &p,int l,int r,int pl = -inf,int pr = inf) {
	if(!p) return 0;
	if(l <= pl && pr <= r) return t[p];
	int res = 0;
	if(l <= mid) res += query(ls[p],l,r,pl,mid);
	if(r > mid) res += query(rs[p],l,r,mid+1,pr);
	return res;
}

#undef ls 
#undef rs 
#undef mid 
}

int ans = 0;

signed main() {

	n = rd(),l = rd(),r = rd();
	for(int i = 1;i<=n;i++) a[i] = rd();
	for(int i = 1;i<=n;i++) a[i] = a[i-1] + a[i];

	sgt::update(sgt::root,a[0],1);
	
	for(int i = 1;i<=n;i++) {
		ans += sgt::query(sgt::root,a[i] - r,a[i] - l);
		sgt::update(sgt::root,a[i],1);
	}

	wt(ans);
	putchar('\n');
	return 0;
}

P3960 [NOIP2017 提高组] 列队

题面:

题目背景

NOIP2017 D2T3

题目描述

Sylvia 是一个热爱学习的女孩子。

前段时间,Sylvia 参加了学校的军训。众所周知,军训的时候需要站方阵。

Sylvia 所在的方阵中有 n × m n \times m n×m 名学生,方阵的行数为 n n n,列数为 m m m

为了便于管理,教官在训练开始时,按照从前到后,从左到右的顺序给方阵中 的学生从 1 1 1 n × m n \times m n×m 编上了号码(参见后面的样例)。即:初始时,第 i i i 行第 j j j 列 的学生的编号是 ( i − 1 ) × m + j (i-1)\times m + j (i1)×m+j

然而在练习方阵的时候,经常会有学生因为各种各样的事情需要离队。在一天 中,一共发生了 q q q 件这样的离队事件。每一次离队事件可以用数对 ( x , y ) ( 1 ≤ x ≤ n , 1 ≤ y ≤ m ) (x,y) (1 \le x \le n, 1 \le y \le m) (x,y)(1xn,1ym) >描述,表示第 x x x 行第 y y y 列的学生离队。

在有学生离队后,队伍中出现了一个空位。为了队伍的整齐,教官会依次下达 这样的两条指令:

  1. 向左看齐。这时第一列保持不动,所有学生向左填补空缺。不难发现在这条 指令之后,空位在第 x x x 行第 m m m 列。
  2. 向前看齐。这时第一行保持不动,所有学生向前填补空缺。不难发现在这条 指令之后,空位在第 n n n 行第 m m m 列。

教官规定不能有两个或更多学生同时离队。即在前一个离队的学生归队之后, 下一个学生才能离队。因此在每一个离队的学生要归队时,队伍中有且仅有第 n n n 行 第 m m m 列一个空位,这时这个学生会自然地填补到这个位置。

因为站方阵真的很无聊,所以 Sylvia 想要计算每一次离队事件中,离队的同学 的编号是多少。

注意:每一个同学的编号不会随着离队事件的发生而改变,在发生离队事件后 方阵中同学的编号可能是乱序的。

输入格式

输入共 q + 1 q+1 q+1 行。

第一行包含 3 3 3 个用空格分隔的正整数 n , m , q n, m, q n,m,q,表示方阵大小是 n n n m m m 列,一共发 生了 q q q 次事件。

接下来 q q q 行按照事件发生顺序描述了 q q q 件事件。每一行是两个整数 x , y x, y x,y,用一个空 格分隔,表示这个离队事件中离队的学生当时排在第 x x x 行第 y y y 列。

输出格式

按照事件输入的顺序,每一个事件输出一行一个整数,表示这个离队事件中离队学生的编号。

样例 #1
样例输入 #1
2 2 3 
1 1 
2 2 
1 2
样例输出 #1
1
1
4
提示

【输入输出样例 1 1 1 说明】

[ 1 2 3 4 ] ⇒ [ 2 3 4 ] ⇒ [ 2 3 4 ] ⇒ [ 2 4 3 ] ⇒ [ 2 4 3 1 ] [ 2 4 3 1 ] ⇒ [ 2 4 3 ] ⇒ [ 2 4 3 ] ⇒ [ 2 4 3 ] ⇒ [ 2 4 3 1 ] [ 2 4 3 1 ] ⇒ [ 2 3 1 ] ⇒ [ 2 3 1 ] ⇒ [ 2 1 3 ] ⇒ [ 2 1 3 4 ] \begin{matrix} \begin{bmatrix} 1 & 2 \\ 3 & 4 \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} & 2 \\ 3 & 4 \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} 2 & \\ 3 & 4 \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} 2 & 4 \\ 3 & \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} 2 & 4 \\ 3 & 1 \\ \end{bmatrix} \\[1em] \begin{bmatrix} 2 & 4 \\ 3 & 1 \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} 2 & 4 \\ 3 & \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} 2 & 4 \\ 3 & \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} 2 & 4 \\ 3 & \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} 2 & 4 \\ 3 & 1 \\ \end{bmatrix}\\[1em] \begin{bmatrix} 2 & 4 \\ 3 & 1 \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} 2 & \\ 3 & 1 \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} 2 & \\ 3 & 1 \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} 2 & 1 \\ 3 & \\ \end{bmatrix} & \Rightarrow & \begin{bmatrix} 2 & 1 \\ 3 & 4 \\ \end{bmatrix} \end{matrix} [1324][2341][2341][324][234][231][234][234][231][234][234][231][2341][2341][2314]

列队的过程如上图所示,每一行描述了一个事件。 在第一个事件中,编号为 1 1 1 的同学离队,这时空位在第一行第一列。接着所有同学 向左标齐,这时编号为 2 2 2 的同学向左移动一步,空位移动到第一行第二列。然后所有同 学向上标齐,这时编号为 4 4 4 的同学向上一步,这时空位移动到第二行第二列。最后编号为 1 1 1 的同学返回填补到空位中。

【数据规模与约定】

测试点编号 n n n m m m q q q其他约定
1 ∼ 6 1\sim 6 16 ≤ 1 0 3 \le 10^3 103 ≤ 1 0 3 \le 10^3 103 ≤ 500 \le 500 500
7 ∼ 10 7\sim 10 710 ≤ 5 × 1 0 4 \le 5\times 10^4 5×104 ≤ 5 × 1 0 4 \le 5\times 10^4 5×104 ≤ 500 \le 500 500
11 ∼ 12 11\sim 12 1112 = 1 =1 =1 ≤ 1 0 5 \le 10^5 105 ≤ 1 0 5 \le 10^5 105所有事件 x = 1 x=1 x=1
13 ∼ 14 13\sim 14 1314 = 1 =1 =1 ≤ 3 × 1 0 5 \le 3\times 10^5 3×105 ≤ 3 × 1 0 5 \le 3\times 10^5 3×105所有事件 x = 1 x=1 x=1
15 ∼ 16 15\sim 16 1516 ≤ 3 × 1 0 5 \le 3\times 10^5 3×105 ≤ 3 × 1 0 5 \le 3\times 10^5 3×105 ≤ 3 × 1 0 5 \le 3\times 10^5 3×105所有事件 x = 1 x=1 x=1
17 ∼ 18 17\sim 18 1718 ≤ 1 0 5 \le 10^5 105 ≤ 1 0 5 \le 10^5 105 ≤ 1 0 5 \le 10^5 105
19 ∼ 20 19\sim 20 1920 ≤ 3 × 1 0 5 \le 3\times 10^5 3×105 ≤ 3 × 1 0 5 \le 3\times 10^5 3×105 ≤ 3 × 1 0 5 \le 3\times 10^5 3×105

数据保证每一个事件满足 1 ≤ x ≤ n , 1 ≤ y ≤ m 1 \le x \le n,1 \le y \le m 1xn,1ym

待补

P8818 [CSP-S 2022] 策略游戏

题面:

题目描述

小 L 和小 Q 在玩一个策略游戏。

有一个长度为 n n n 的数组 A A A 和一个长度为 m m m 的数组 B B B,在此基础上定义一个大小为 n × m n \times m n×m 的矩阵 C C C,满足 C i j = A i × B j C_{i j} = A_i \times B_j Cij=Ai×Bj。所有>下标均从 1 1 1 开始。

游戏一共会进行 q q q 轮,在每一轮游戏中,会事先给出 4 4 4 个参数 l 1 , r 1 , l 2 , r 2 l_1, r_1, l_2, r_2 l1,r1,l2,r2,满足 1 ≤ l 1 ≤ r 1 ≤ n 1 \le l_1 \le r_1 \le n 1l1r1n 1 ≤ l 2 ≤ r 2 ≤ m 1 \le l_2 \le r_2 \le m 1l2r2m

游戏中,小 L 先选择一个 l 1 ∼ r 1 l_1 \sim r_1 l1r1 之间的下标 x x x,然后小 Q 选择一个 l 2 ∼ r 2 l_2 \sim r_2 l2r2 之间的下标 y y y。定义这一轮游戏中二人的得分是 C x y C_{x y} Cxy

小 L 的目标是使得这个得分尽可能大,小 Q 的目标是使得这个得分尽可能小。同时两人都是足够聪明的玩家,每次都会采用最优的策略。

请问:按照二人的最优策略,每轮游戏的得分分别是多少?

输入格式

第一行输入三个正整数 n , m , q n, m, q n,m,q,分别表示数组 A A A,数组 B B B 的长度和游戏轮数。

第二行: n n n 个整数,表示 A i A_i Ai,分别表示数组 A A A 的元素。

第三行: m m m 个整数,表示 B i B_i Bi,分别表示数组 B B B 的元素。

接下来 q q q 行,每行四个正整数,表示这一次游戏的 l 1 , r 1 , l 2 , r 2 l_1, r_1, l_2, r_2 l1,r1,l2,r2

输出格式

输出共 q q q 行,每行一个整数,分别表示每一轮游戏中,小 L 和小 Q 在最优策略下的得分。

样例 #1
样例输入 #1
3 2 2
0 1 -2
-3 4
1 3 1 2
2 3 2 2
样例输出 #1
0
4
样例 #2
样例输入 #2
6 4 5
3 -1 -2 1 2 0
1 2 -1 -3
1 6 1 4
1 5 1 4
1 4 1 2
2 6 3 4
2 5 2 3
样例输出 #2
0
-2
3
2
-1
提示

【样例解释 #1】

这组数据中,矩阵 C C C 如下:

[ 0 0 − 3 4 6 − 8 ] \begin{bmatrix} 0 & 0 \\ -3 & 4 \\ 6 & -8 \end{bmatrix} 036048

在第一轮游戏中,无论小 L 选取的是 x = 2 x = 2 x=2 还是 x = 3 x = 3 x=3,小 Q 都有办法选择某个 y y y 使得最终的得分为负数。因此小 L 选择 x = 1 x = 1 x=1 是最优的,因为这样得>分一定为 0 0 0

而在第二轮游戏中,由于小 L 可以选 x = 2 x = 2 x=2,小 Q 只能选 y = 2 y = 2 y=2,如此得分为 4 4 4

【样例 #3】

见附件中的 game/game3.ingame/game3.ans

【样例 #4】

见附件中的 game/game4.ingame/game4.ans

【数据范围】

对于所有数据, 1 ≤ n , m , q ≤ 10 5 1 \le n, m, q \le {10}^5 1n,m,q105 − 10 9 ≤ A i , B i ≤ 10 9 -{10}^9 \le A_i, B_i \le {10}^9 109Ai,Bi109。对于每轮游戏而言, 1 ≤ l 1 ≤ r 1 ≤ n 1 \le l_1 \le r_1 \le n 1l1r1n 1 ≤ l 2 ≤ r 2 ≤ m 1 \le l_2 \le r_2 \le m 1l2r2m

测试点编号 n , m , q ≤ n, m, q \le n,m,q特殊条件
1 1 1 200 200 2001, 2
2 2 2 200 200 2001
3 3 3 200 200 2002
4 ∼ 5 4 \sim 5 45 200 200 200
6 6 6 1000 1000 10001, 2
7 ∼ 8 7 \sim 8 78 1000 1000 10001
9 ∼ 10 9 \sim 10 910 1000 1000 10002
11 ∼ 12 11 \sim 12 1112 1000 1000 1000
13 13 13 10 5 {10}^5 1051, 2
14 ∼ 15 14 \sim 15 1415 10 5 {10}^5 1051
16 ∼ 17 16 \sim 17 1617 10 5 {10}^5 1052
18 ∼ 20 18 \sim 20 1820 10 5 {10}^5 105

其中,特殊性质 1 为:保证 A i , B i > 0 A_i, B_i > 0 Ai,Bi>0
特殊性质 2 为:保证对于每轮游戏而言,要么 l 1 = r 1 l_1 = r_1 l1=r1,要么 l 2 = r 2 l_2 = r_2 l2=r2

鉴定为 大分讨

我们直接来分析局面

上图!

首先来分析简单的

我们假设 L L L 先手, Q Q Q 后手

情况 1 ◯ \textcircled{1} 1

img

L , Q L,Q LQ 都在 0 0 0 左侧

L L L 肯定选区间最大值, Q Q Q 肯定选区间最小值

情况 2 ◯ \textcircled{2} 2

img

L , Q L,Q LQ 都在 0 0 0 右侧

L L L 肯定选区间最小值, Q Q Q 肯定选区间最大值

情况 3 ◯ \textcircled{3} 3

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

L L L 肯定选区间最小值, Q Q Q 肯定选区间最小值

情况 4 ◯ \textcircled{4} 4

img

L L L 肯定选区间最小值, Q Q Q 肯定选区间最大值

再然后就比较难想了

情况 5 ◯ \textcircled{5} 5

img

L L L 肯定选区间最大值, Q Q Q 肯定选区间最小值

情况 6 ◯ \textcircled{6} 6

img

L L L 肯定选区间最小值, Q Q Q 肯定选区间最大值

最困难的

情况 7 ◯ → 一般情况 \textcircled{7} \rightarrow 一般情况 7一般情况

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作为 L L L,显然取最大值、最小值,都会被反杀

那么,我们就让损失最小化

容易想到,我们取 L , Q L,Q L,Q 靠近 0 0 0 两侧的值

如图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样,损失最小

那么我们要特别处理 L L L 序列的非正最大值,非负最小值—我们在线段树里额外维护一下就可以了

此题完美结束!

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int M = 1e5+5,inf = 2139062143;

int n,m,q,a[M];

struct sgt{
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((pl + pr) >> 1)

int mn[M<<2],mx[M<<2];
int _mn[M<<2],_mx[M<<2];

void push_up(int p) {
	mn[p] = min(mn[ls],mn[rs]);
	mx[p] = max(mx[ls],mx[rs]);
	_mn[p] = min(_mn[ls],_mn[rs]);
	_mx[p] = max(_mx[ls],_mx[rs]);
}

void build(int p,int pl,int pr){
	if(pl == pr) {
		mn[p] = mx[p] = a[pl];
		_mn[p] = a[pl] >= 0 ? a[pl] : inf;
		_mx[p] = a[pl] <= 0 ? a[pl] : -inf;
		return;
	}
	build(ls,pl,mid);
	build(rs,mid+1,pr);
	push_up(p);
}

int querymax(int p,int pl,int pr,int l,int r) {
	if(l <= pl && pr <= r) return mx[p];
	int res = -inf;
	if(l <= mid) res = max(res,querymax(ls,pl,mid,l,r));
	if(r > mid) res = max(res,querymax(rs,mid+1,pr,l,r));
	return res;
}

int query_max(int p,int pl,int pr,int l,int r) {
	if(l <= pl && pr <= r) return _mx[p];
	int res = -inf;
	if(l <= mid) res = max(res,query_max(ls,pl,mid,l,r));
	if(r > mid) res = max(res,query_max(rs,mid+1,pr,l,r));
	return res;
}

int query_min(int p,int pl,int pr,int l,int r){
	if(l <= pl && pr <= r) return _mn[p];
	int res = inf;
	if(l <= mid) res = min(res,query_min(ls,pl,mid,l,r));
	if(r > mid) res = min(res,query_min(rs,mid+1,pr,l,r));
	return res;
}

int querymin(int p,int pl,int pr,int l,int r) {
	if(l <= pl && pr <= r) return mn[p];
	int res = inf;
	if(l <= mid) res = min(res,querymin(ls,pl,mid,l,r));
	if(r > mid) res = min(res,querymin(rs,mid+1,pr,l,r));
	return res;
}

}c,d;

signed main() {	
	n = rd(),m = rd(),q = rd();
	for(int i = 1;i<=n;i++) a[i] = rd();
	c.build(1,1,n);
	for(int i = 1;i<=m;i++) a[i] = rd();
	d.build(1,1,m);

	while(q--) {
		int l1 = rd(),r1 = rd(),l2 = rd(),r2 = rd();
		int mna = c.querymin(1,1,n,l1,r1),mnb = d.querymin(1,1,m,l2,r2);
		int mxa = c.querymax(1,1,n,l1,r1),mxb = d.querymax(1,1,m,l2,r2);
		if(mna >= 0 && mnb >= 0) wt(mxa * mnb);
		else if(mxa <= 0 && mxb <= 0) wt(mna * mxb);
		else if(mna >= 0 && mnb <= 0) wt(mna * mnb);
		else if(mxa <= 0 && mxb >= 0) wt(mxb * mxa);
		else if(mna <= 0 && mxa >= 0 && mnb >= 0) wt(mxa * mnb);
		else if(mxb <= 0 && mxa >= 0 && mna <= 0) wt(mna * mxb); 
		else{
			int ra = c.query_min(1,1,n,l1,r1),rb = d.querymin(1,1,m,l2,r2);
			int ora = c.query_max(1,1,n,l1,r1),orb = d.querymax(1,1,m,l2,r2);
			int res = max(ra * rb,ora * orb);
			wt(res);
		}  
		putchar('\n');
	}


	return 0;
}

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值