题解
A Grass Field
题面翻译
给出一个 2 × 2 2 \times 2 2×2 的矩阵,矩阵的值都是 0 0 0 和 1 1 1,定义一次操作:选择一个点,将其所在的行和列的点的值全部修改为 0 0 0,求最少几次操作将整个矩阵修改为 0 0 0。
by @Leonador
题目描述
There is a field of size 2 × 2 2 \times 2 2×2 . Each cell of this field can either contain grass or be empty. The value a i , j a_{i, j} ai,j is 1 1 1 if the cell ( i , j ) (i, j) (i,j) contains grass, or 0 0 0 otherwise.
In one move, you can choose one row and one column and cut all the grass in this row and this column. In other words, you choose the row x x x and the column y y y , then you cut the grass in all cells a x , i a_{x, i} ax,i and all cells a i , y a_{i, y} ai,y for all i i i from 1 1 1 to 2 2 2 . After you cut the grass from a cell, it becomes empty (i. e. its value is replaced by 0 0 0 ).
Your task is to find the minimum number of moves required to cut the grass in all non-empty cells of the field (i. e. make all a i , j a_{i, j} ai,j zeros).
You have to answer t t t independent test cases.
输入格式
The first line of the input contains one integer t t t ( 1 ≤ t ≤ 16 1 \le t \le 16 1≤t≤16 ) — the number of test cases. Then t t t test cases follow.
The test case consists of two lines, each of these lines contains two integers. The $ j $ -th integer in the $ i $ -th row is a i , j a_{i, j} ai,j . If a i , j = 0 a_{i, j} = 0 ai,j=0 then the cell ( i , j ) (i, j) (i,j) is empty, and if a i , j = 1 a_{i, j} = 1 ai,j=1 the cell ( i , j ) (i, j) (i,j) contains grass.
输出格式
For each test case, print one integer — the minimum number of moves required to cut the grass in all non-empty cells of the field (i. e. make all a i , j a_{i, j} ai,j zeros) in the corresponding test case.
样例 #1
样例输入 #1
3
0 0
0 0
1 0
0 1
1 1
1 1
样例输出 #1
0
1
2
解析
选择一行和一列,那么最多可以一次删除3个数,所以只需要1判断四个数的和就行了。 0 0 0输出 0 0 0, 4 4 4输出 2 2 2,其余都是 1 1 1。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5;
int n, m;
int a[N][N];
void solve()
{
int cnt = 0;
for(int i = 1; i <= 2; i ++)
{
for(int j = 1; j <= 2; j ++)
{
scanf("%d", &a[i][j]);
if(a[i][j] == 1)
{
cnt ++;
}
}
}
if(cnt == 4)
{
printf("2\n");
}
else if(cnt < 4 && cnt > 0)
{
printf("1\n");
}
else printf("0\n");
}
int main()
{
int t;
cin >> t;
while(t --)
{
solve();
}
}
B Permutation
题目描述
Recall that a permutation of length n n n is an array where each element from 1 1 1 to n n n occurs exactly once.
For a fixed positive integer d d d , let’s define the cost of the permutation p p p of length n n n as the number of indices i i i ( 1 ≤ i < n ) (1 \le i < n) (1≤i<n) such that p i ⋅ d = p i + 1 p_i \cdot d = p_{i + 1} pi⋅d=pi+1 .
For example, if d = 3 d = 3 d=3 and p = [ 5 , 2 , 6 , 7 , 1 , 3 , 4 ] p = [5, 2, 6, 7, 1, 3, 4] p=[5,2,6,7,1,3,4] , then the cost of such a permutation is 2 2 2 , because p 2 ⋅ 3 = p 3 p_2 \cdot 3 = p_3 p2⋅3=p3 and p 5 ⋅ 3 = p 6 p_5 \cdot 3 = p_6 p5⋅3=p6 .
Your task is the following one: for a given value n n n , find the permutation of length n n n and the value d d d with maximum possible cost (over all ways to choose the permutation and d d d ). If there are multiple answers, then print any of them.
输入格式
The first line contains a single integer t t t ( 1 ≤ t ≤ 500 1 \le t \le 500 1≤t≤500 ) — the number of test cases.
The single line of each test case contains a single integer n n n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 2 \le n \le 2 \cdot 10^5 2≤n≤2⋅105 ).
The sum of n n n over all test cases does not exceed 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105 .
输出格式
For each test case, print the value d d d in the first line, and n n n integers in the second line — the permutation itself. If there are multiple answers, then print any of them.
样例 #1
样例输入 #1
2
2
3
样例输出 #1
2
1 2
3
2 1 3
解析
这道题就是最大化数组后一个数是前一个数的倍数的个数,那么可以直接贪心2倍是最多的。因为这道题不需要 d d d最大化(样例纯误导人)就算最大化答案不是 2 2 2就是 3 3 3,所以本代码的写法更为复杂且没用。直接从小到大枚举倍数模拟即可。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n, m;
bool st[N];
void solve()
{
scanf("%d", &n);
int maxv = 0;
int w = log2(n);
int r = 0, cnt = 0;
if(n >= 3)
{
r = 3, cnt = 1;
while(r <= n)
{
r = r * 3;
if(r <= n) cnt ++;
}
}
else cnt = 0;
//cout << w << " " << cnt << endl;
int x = cnt;
for(int i = 1; i <= n; i ++) st[i] = 0;
if(x == w || w < x)
{
puts("3");
printf("1 ");
r = 3;
while(r <= n)
{
st[r] = 1;
printf("%d ", r);
r = r * 3;
}
for(int i = 2; i <= n; i ++)
{
for(int j = i; j <= n; j *= 3)
{
if(st[j]) continue;
else
{
st[j] = true;
printf("%d ", j);
}
}
}
}
else
{
puts("2");
printf("1 ");
for(int i = 2; i <= n; i ++)
{
for(int j = i; j <= n; j *= 2)
{
if(st[j]) continue;
else
{
st[j] = true;
printf("%d ", j);
}
}
}
}
printf("\n");
}
int main()
{
int t;
cin >> t;
while(t --)
{
solve();
}
}
C Schedule Management
题面翻译
题意简述
有 n n n 个工人和 m m m 个任务,每个任务都有且仅有一个工人擅长做,如果让擅长做的工人去做,那么要花一个单位时间,否则要花两个单位时间。请问完成所有的任务至少要花多少时间。
注意:每项工作只能由一个工人完成,不能合作完成。
输入格式
本题多测,对于每组数据,第一行两个整数 n n n 和 m m m。第二行 m m m 个数,表示每项工作所擅长的工人编号。
输出格式
每组数据一个整数表示最少要花的时间。
题目描述
There are n n n workers and m m m tasks. The workers are numbered from 1 1 1 to n n n . Each task i i i has a value a i a_i ai — the index of worker who is proficient in this task.
Every task should have a worker assigned to it. If a worker is proficient in the task, they complete it in 1 1 1 hour. Otherwise, it takes them 2 2 2 hours.
The workers work in parallel, independently of each other. Each worker can only work on one task at once.
Assign the workers to all tasks in such a way that the tasks are completed as early as possible. The work starts at time 0 0 0 . What’s the minimum time all tasks can be completed by?
输入格式
The first line contains a single integer t t t ( 1 ≤ t ≤ 1 0 4 1 \le t \le 10^4 1≤t≤104 ) — the number of testcases.
The first line of each testcase contains two integers n n n and m m m ( 1 ≤ n ≤ m ≤ 2 ⋅ 1 0 5 1 \le n \le m \le 2 \cdot 10^5 1≤n≤m≤2⋅105 ) — the number of workers and the number of tasks.
The second line contains m m m integers a 1 , a 2 , … , a m a_1, a_2, \dots, a_m a1,a2,…,am ( 1 ≤ a i ≤ n 1 \le a_i \le n 1≤ai≤n ) — the index of the worker proficient in the i i i -th task.
The sum of m m m over all testcases doesn’t exceed 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105 .
输出格式
For each testcase, print a single integer — the minimum time all tasks can be completed by.
样例 #1
样例输入 #1
4
2 4
1 2 1 2
2 4
1 1 1 1
5 5
5 1 3 2 4
1 1
1
样例输出 #1
2
3
1
1
提示
In the first testcase, the first worker works on tasks 1 1 1 and 3 3 3 , and the second worker works on tasks 2 2 2 and 4 4 4 . Since they both are proficient in the corresponding tasks, they take 1 1 1 hour on each. Both of them complete 2 2 2 tasks in 2 2 2 hours. Thus, all tasks are completed by 2 2 2 hours.
In the second testcase, it’s optimal to assign the first worker to tasks 1 , 2 1, 2 1,2 and 3 3 3 and the second worker to task 4 4 4 . The first worker spends 3 3 3 hours, the second worker spends 2 2 2 hours (since they are not proficient in the taken task).
In the third example, each worker can be assigned to the task they are proficient at. Thus, each of them complete their task in 1 1 1 hour.
解析
这道题需要我们算出工人工作的最短时间,我们需要迅速反应出这道题的时间具有二分性。因为最短时间之前做不完,最短时间及以后都能做完,那么我们就需要去 O ( n ) O(n) O(n)检查是否做完就行了。假设有时间 t t t然后第 i i i擅长 a i a_i ai个工作,那么让 w = m i n ( t , a [ i ] ) w=min(t,a[i]) w=min(t,a[i]),如果 w = = a [ i ] w==a[i] w==a[i]那么还有空余时间,空余时间可以做 t − w 2 \frac{t-w}{2} 2t−w个任务。否则还需要 ( t − w ) (t-w) (t−w)的工作需要做完(注意不是时间)。然后其他同理,如果空余的任务数大于等于需要做完的任务,那么就可以做。代码实现较为容易,需要注意开 l o n g l o n g longlong longlong。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n, m;
int a[N];
bool check(ll t)
{
ll re = 0;
ll nt = 0;
for(int i = 1; i <= n; i ++)
{
ll w = min(t, 1ll * a[i]);
if(w == a[i])
{
re += (t - w) / 2;
}
else
{
nt += (a[i] - w);
}
}
//printf("%d %d %d\n", re, nt, t);
if(re >= nt) return true;
else return false;
}
void solve()
{
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i ++) a[i] = 0;
for(int i = 1; i <= m; i ++)
{
int t;
scanf("%d", &t);
a[t] ++;
}
int l = 1, r = 2 * m;
while(l < r)
{
int mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", l);
}
int main()
{
int t;
cin >> t;
while(t --)
{
solve();
}
}
D Permutation Restoration
题目描述
Monocarp had a permutation a a a of n n n integers 1 1 1 , 2 2 2 , …, n n n (a permutation is an array where each element from $ 1 $ to $ n $ occurs exactly once).
Then Monocarp calculated an array of integers b b b of size n n n , where b i = ⌊ i a i ⌋ b_i = \left\lfloor \frac{i}{a_i} \right\rfloor bi=⌊aii⌋ . For example, if the permutation a a a is [ 2 , 1 , 4 , 3 ] [2, 1, 4, 3] [2,1,4,3] , then the array b b b is equal to [ ⌊ 1 2 ⌋ , ⌊ 2 1 ⌋ , ⌊ 3 4 ⌋ , ⌊ 4 3 ⌋ ] = [ 0 , 2 , 0 , 1 ] \left[ \left\lfloor \frac{1}{2} \right\rfloor, \left\lfloor \frac{2}{1} \right\rfloor, \left\lfloor \frac{3}{4} \right\rfloor, \left\lfloor \frac{4}{3} \right\rfloor \right] = [0, 2, 0, 1] [⌊21⌋,⌊12⌋,⌊43⌋,⌊34⌋]=[0,2,0,1] .
Unfortunately, the Monocarp has lost his permutation, so he wants to restore it. Your task is to find a permutation a a a that corresponds to the given array b b b . If there are multiple possible permutations, then print any of them. The tests are constructed in such a way that least one suitable permutation exists.
输入格式
The first line contains a single integer t t t ( 1 ≤ t ≤ 1 0 5 1 \le t \le 10^5 1≤t≤105 ) — number of test cases.
The first line of each test case contains a single integer n n n ( 1 ≤ n ≤ 5 ⋅ 1 0 5 1 \le n \le 5 \cdot 10^5 1≤n≤5⋅105 ).
The second line contains $ n $ integers b 1 , b 2 , … , b n b_1, b_2, \dots, b_n b1,b2,…,bn ( 0 ≤ b i ≤ n 0 \le b_i \le n 0≤bi≤n ).
Additional constrains on the input:
- the sum of $ n $ over test cases does not exceed 5 ⋅ 1 0 5 5 \cdot 10^5 5⋅105 ;
- there exists at least one permutation a a a that would yield this array b b b .
输出格式
For each test case, print n n n integers — a permutation a a a that corresponds to the given array b b b . If there are multiple possible permutations, then print any of them.
样例 #1
样例输入 #1
4
4
0 2 0 1
2
1 1
5
0 0 1 4 1
3
0 1 3
样例输出 #1
2 1 4 3
1 2
3 4 2 1 5
3 2 1
解析
这道题告诉我们 b b b数组我们需要通过转换求出一组排列 a a a使得满足对任意 ( 1 ≤ i ≤ n ) b i = ⌊ i a i ⌋ (1\le i \le n) b_i = \left\lfloor \frac{i}{a_i} \right\rfloor (1≤i≤n)bi=⌊aii⌋。我们可以知道每一个 a i a_i ai的值范围。如果已一个数满足 b i = ⌊ i a i ⌋ b_i = \left\lfloor \frac{i}{a_i} \right\rfloor bi=⌊aii⌋那么 a i ∈ [ i b i + 1 + 1 , i b i ] a_i\in[\frac{i}{b_i+1}+1,\frac{i}{b_i}] ai∈[bi+1i+1,bii]。这样我们就可以在 O ( n ) O(n) O(n)的时间算出每一个位置的数的取值范围。现在问题就转化成为,给出 n n n个数的范围,然后找到一个合法的排列使得每一个关系都成立。这个问题是一个比较经典的问题,叫做活动安排。从直观上讲,应该是按照右边界作为第一关键字排序,做边界作为第二关键字。开一个 v e c t o r [ N ] vector[N] vector[N]来存以L开始的所有范围。然后枚举 1 1 1到 n n n,把所有以 i i i开始的所有范围按照 [ R , p o s ] [R,pos] [R,pos]的格式存到优先队列当中,那么这样安排一定是最优的,因为它满足了我每一次分配的是“最需要”的节点。然后将 a n s [ p o s ] = i ans[pos]=i ans[pos]=i即可,然后输出所有答案。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
int n, m;
typedef pair<int,int> PII;
int b[N];
vector<PII> g[N];
priority_queue<PII,vector<PII>, greater<PII>> heap;
int a[N];
void solve()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++)
{
g[i].clear();
}
for(int i = 1; i <= n; i ++)
{
scanf("%d", &b[i]);
}
for(int i = 1; i <= n; i ++)
{
int L = i / (b[i] + 1) + 1;
int R = b[i] == 0 ? n : i / b[i];
g[L].push_back({R, i});
}
for(int i = 1; i <= n; i ++)
{
for(PII p:g[i]) heap.push(p);
PII p = heap.top();
heap.pop();
a[p.second] = i;
}
for(int i = 1; i <= n; i ++)
{
printf("%d ", a[i]);
}
printf("\n");
}
int main()
{
int t;
cin >> t;
while(t --)
{
solve();
}
}
E Text Editor
题目描述
You wanted to write a text t t t consisting of m m m lowercase Latin letters. But instead, you have written a text s s s consisting of n n n lowercase Latin letters, and now you want to fix it by obtaining the text t t t from the text s s s .
Initially, the cursor of your text editor is at the end of the text s s s (after its last character). In one move, you can do one of the following actions:
- press the “left” button, so the cursor is moved to the left by one position (or does nothing if it is pointing at the beginning of the text, i. e. before its first character);
- press the “right” button, so the cursor is moved to the right by one position (or does nothing if it is pointing at the end of the text, i. e. after its last character);
- press the “home” button, so the cursor is moved to the beginning of the text (before the first character of the text);
- press the “end” button, so the cursor is moved to the end of the text (after the last character of the text);
- press the “backspace” button, so the character before the cursor is removed from the text (if there is no such character, nothing happens).
Your task is to calculate the minimum number of moves required to obtain the text t t t from the text s s s using the given set of actions, or determine it is impossible to obtain the text t t t from the text s s s .
You have to answer $ T $ independent test cases.
输入格式
The first line of the input contains one integer T T T ( 1 ≤ T ≤ 5000 1 \le T \le 5000 1≤T≤5000 ) — the number of test cases. Then T T T test cases follow.
The first line of the test case contains two integers n n n and m m m ( 1 ≤ m ≤ n ≤ 5000 1 \le m \le n \le 5000 1≤m≤n≤5000 ) — the length of s s s and the length of t t t , respectively.
The second line of the test case contains the string s s s consisting of n n n lowercase Latin letters.
The third line of the test case contains the string t t t consisting of m m m lowercase Latin letters.
It is guaranteed that the sum of n n n over all test cases does not exceed 5000 5000 5000 ( ∑ n ≤ 5000 \sum n \le 5000 ∑n≤5000 ).
输出格式
For each test case, print one integer — the minimum number of moves required to obtain the text t t t from the text s s s using the given set of actions, or -1 if it is impossible to obtain the text t t t from the text s s s in the given test case.
样例 #1
样例输入 #1
6
9 4
aaaaaaaaa
aaaa
7 3
abacaba
aaa
5 4
aabcd
abcd
4 2
abba
bb
6 4
baraka
baka
8 7
question
problem
样例输出 #1
5
6
3
4
4
-1
解析
这道题先看数据范围, n ≤ 5000 n\le5000 n≤5000时间复杂度就是 n 2 n^2 n2,根据学习动态规划的经验,有一道动态规划叫做编辑距离,这道题和本题非常相似,所以通过动态规划来解决这道题。这道题有几种操作,第一种是将光标移到左边一格,第二种是把光标移到右边一格,第三种是把光标移到最左边,第四种是把光标移到最右边,最后一种是删除光标前面的一段字符。光标一开始是在最右边,那么通过贪心的想法,删除操作肯定不包含将光标移到最右边,应该是从右删除到左,然后突然跳跃一段距离,然后从左往右删除,这样的删除顺序是最优的。所以我们需要考虑三种:从前往后删除,中间完全匹配,后面从后往前删除。那么我们可以假设 d p [ i ] [ j ] dp[i][j] dp[i][j]是 s s s串匹配了前 i i i为, t t t串匹配了前 j j j位时的操作次数。接下来分别讨论状态转移方程的书写,下标都是0开始,注意不能越界。
1.
1.
1.前缀:
初始状态
d
p
[
0
]
[
0
]
=
1
dp[0][0]=1
dp[0][0]=1因为光标移到前面需要操作一次,其余都是正无穷。
d
p
[
i
+
1
]
[
j
]
=
m
i
n
(
d
p
[
i
+
1
]
[
j
]
,
d
p
[
i
]
[
j
]
+
2
)
dp[i + 1][j] = min(dp[i + 1][j], dp[i][j] + 2)
dp[i+1][j]=min(dp[i+1][j],dp[i][j]+2) 未匹配上,需要光标右移一次,删除一次。
d
p
[
i
+
1
]
[
j
+
1
]
=
m
i
n
(
d
p
[
i
+
1
]
[
j
+
1
]
,
d
p
[
i
]
[
j
]
+
1
)
(
s
[
i
]
=
=
t
[
j
]
)
dp[i + 1][j + 1]=min(dp[i + 1][j + 1], dp[i][j] + 1) (s[i] == t[j])
dp[i+1][j+1]=min(dp[i+1][j+1],dp[i][j]+1)(s[i]==t[j])匹配上,光标右移。
2.
2.
2.中缀
初始状态
d
p
[
0
]
[
0
]
=
0
dp[0][0]=0
dp[0][0]=0其余值不变。
d
p
[
i
+
1
]
[
j
+
1
]
=
m
i
n
(
d
p
[
i
+
1
]
[
j
+
1
]
,
d
p
[
i
]
[
j
]
)
(
s
[
i
]
=
=
t
[
j
]
)
dp[i + 1][j + 1]=min(dp[i + 1][j + 1],dp[i][j]) (s[i]==t[j])
dp[i+1][j+1]=min(dp[i+1][j+1],dp[i][j])(s[i]==t[j])这一段直接跳过,所以操作数是0。
3.
3.
3.后缀
初始状态
d
p
[
0
]
[
0
]
=
0
dp[0][0]=0
dp[0][0]=0其余值不变。
d
p
[
i
+
1
]
[
j
]
=
m
i
n
(
d
p
[
i
+
1
]
[
j
]
,
d
p
[
i
]
[
j
]
+
1
)
dp[i + 1][j] = min(dp[i + 1][j], dp[i][j] + 1)
dp[i+1][j]=min(dp[i+1][j],dp[i][j]+1) 未匹配上,需要光标删除一次。
d
p
[
i
+
1
]
[
j
+
1
]
=
m
i
n
(
d
p
[
i
+
1
]
[
j
+
1
]
,
d
p
[
i
]
[
j
]
+
1
)
(
s
[
i
]
=
=
t
[
j
]
)
dp[i + 1][j + 1]=min(dp[i + 1][j + 1], dp[i][j] + 1) (s[i] == t[j])
dp[i+1][j+1]=min(dp[i+1][j+1],dp[i][j]+1)(s[i]==t[j])匹配上,光标左移。
如果 d p [ n ] [ m ] ≠ I N F , a n s = d p [ n ] [ m ] dp[n][m]\ne INF,ans = dp[n][m] dp[n][m]=INF,ans=dp[n][m],否则 a n s = − 1 ans=-1 ans=−1
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5010;
int n, m;
int dp[N][N];
void solve()
{
cin >> n >> m;
string s, t;
cin >> s >> t;
for(int i = 0; i <= n; i ++)
{
for(int j = 0; j <= m; j ++) dp[i][j] = 2e9;
}
dp[0][0] = 1;
for(int i = 0; i <= n; i ++)
{
for(int j = 0; j <= m; j ++)
{
if(i < n) dp[i + 1][j] = min(dp[i + 1][j], dp[i][j] + 2);
if(s[i] == t[j] && i < n && j < m) dp[i + 1][j + 1] = min(dp[i + 1][j + 1], dp[i][j] + 1);
}
}
dp[0][0] = 0;
for(int i = 0; i < n; i ++)
{
for(int j = 0; j < m; j ++)
{
if(s[i] == t[j]) dp[i + 1][j + 1] = min(dp[i + 1][j + 1], dp[i][j]);
}
}
dp[0][0] = 0;
for(int i = 0; i <= n; i ++)
{
for(int j = 0; j <= m; j ++)
{
if(i < n) dp[i + 1][j] = min(dp[i + 1][j], dp[i][j] + 1);
if(s[i] == t[j] && i < n && j < m) dp[i + 1][j + 1] = min(dp[i + 1][j + 1], dp[i][j] + 1);
}
}
if(dp[n][m] == 2e9)
{
printf("-1\n");
}
else
{
printf("%d\n", dp[n][m]);
}
}
int main()
{
int t;
cin >> t;
while(t --)
{
solve();
}
}
F Points
题目描述
A triple of points i i i , j j j and k k k on a coordinate line is called beautiful if i < j < k i < j < k i<j<k and k − i ≤ d k - i \le d k−i≤d .
You are given a set of points on a coordinate line, initially empty. You have to process queries of three types:
- add a point;
- remove a point;
- calculate the number of beautiful triples consisting of points belonging to the set.
输入格式
The first line contains two integers q q q and d d d ( 1 ≤ q , d ≤ 2 ⋅ 1 0 5 1 \le q, d \le 2 \cdot 10^5 1≤q,d≤2⋅105 ) — the number of queries and the parameter for defining if a triple is beautiful, respectively.
The second line contains q q q integers a 1 , a 2 , … , a q a_1, a_2, \dots, a_q a1,a2,…,aq ( 1 ≤ a i ≤ 2 ⋅ 1 0 5 1 \le a_i \le 2 \cdot 10^5 1≤ai≤2⋅105 ) denoting the queries. The integer a i a_i ai denotes the i i i -th query in the following way:
- if the point a i a_i ai belongs to the set, remove it; otherwise, add it;
- after adding or removing the point, print the number of beautiful triples.
输出格式
For each query, print one integer — the number of beautiful triples after processing the respective query.
样例 #1
样例输入 #1
7 5
8 5 3 2 1 5 6
样例输出 #1
0
0
1
2
5
1
5
解析
这道题的要求是计算满足条件的triple的数量,假设 f [ i ] = [ i + 1 , i + d ] f[i] = [i + 1, i + d] f[i]=[i+1,i+d]的数字的数目,那么triple的个数 a n s = ( f [ i ] ∗ ( f [ i ] − 1 ) / 2 ans = (f[i] * (f[i]-1) / 2 ans=(f[i]∗(f[i]−1)/2,那么当插入一个数字x的时候,对于 [ x − d , x − 1 ] [x - d, x - 1] [x−d,x−1]这个区间的贡献是 1 1 1,反之是 − 1 -1 −1,这道题的答案 a n s = ∑ ( f [ i ] A N D [ i 存在 ] ) ans = \sum(f[i] AND [i存在]) ans=∑(f[i]AND[i存在]),所以说需要使用线段树维护区间加减,然后需要维护当前的答案 a n s ans ans,所以还需要维护 i i i后面有几个数 w w w,加入k个数对答案的贡献是 1 2 ∗ ( ( f [ i ] + k ) 2 − ( f [ i ] + k ) ) − 1 2 ∗ ( f [ i ] 2 − f [ i ] ) = k ∗ f [ i ] + k ∗ ( k − 1 ) 2 \frac{1}{2} * ((f[i] + k) ^ 2 - (f[i] + k)) - \frac{1}{2} * (f[i] ^ 2 - f[i]) = k * f[i] +\frac{ k * (k - 1)}{2} 21∗((f[i]+k)2−(f[i]+k))−21∗(f[i]2−f[i])=k∗f[i]+2k∗(k−1),减去k个数对答案的贡献是 1 2 ∗ ( ( f [ i ] + k ) 2 − ( f [ i ] + k ) ) − 1 2 ∗ ( f [ i ] 2 − f [ i ] ) = k ∗ f [ i ] − k ∗ ( k − 1 ) 2 \frac{1}{2} * ((f[i] + k) ^ 2 - (f[i] + k)) - \frac{1}{2} * (f[i] ^ 2 - f[i]) = k * f[i] -\frac{ k * (k - 1)}{2} 21∗((f[i]+k)2−(f[i]+k))−21∗(f[i]2−f[i])=k∗f[i]−2k∗(k−1)。
所以线段数需要维护三个值,一个是答案,一个是 f [ i ] f[i] f[i]的和,一个是存在的数目。在修改的时候同时维护 a n s ans ans,每一次询问的答案就是 t r [ 1 ] . a n s tr[1].ans tr[1].ans,需要注意的是在维护区间和的时候,加上一个常数对一个父节点的贡献是儿子的数目*常数。所以需要维护节点个数 t o t tot tot。还有就是先维护区间和,然后再是单点修改,顺序不能换。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n, m;
int st[N];
struct node
{
ll sum, tot, ans;
ll tag;
}tr[N * 4];
void update(int id)
{
tr[id].ans = tr[id * 2].ans + tr[id * 2 + 1].ans;
tr[id].sum = tr[id * 2].sum + tr[id * 2 + 1].sum;
tr[id].tot = tr[id * 2].tot + tr[id * 2 + 1].tot;
}
void settag(int id,ll val)
{
if(val > 0)
{
tr[id].ans += val * tr[id].sum;
tr[id].ans += val * (val - 1) / 2 * tr[id].tot;
}
else
{
tr[id].ans += val * tr[id].sum;
ll v = -val;
tr[id].ans += v * (v + 1) / 2 * tr[id].tot;
}
tr[id].sum += val * tr[id].tot;
tr[id].tag += val;
}
void pushdown(int id)
{
if(tr[id].tag != 0)
{
settag(id * 2, tr[id].tag);
settag(id * 2 + 1, tr[id].tag);
tr[id].tag = 0;
}
}
void change(int id,int l,int r,int pos, ll val, int tot)
{
if(l == r)
{
tr[id].ans = val * (val - 1) / 2;
tr[id].sum = val;
tr[id].tot = tot;
}
else
{
int mid = l + r >> 1;
pushdown(id);
if(pos <= mid) change(id * 2, l, mid, pos, val, tot);
else change(id * 2 + 1, mid + 1, r, pos, val, tot);
update(id);
}
}
void modify(int id,int l,int r,int ql,int qr,int val)
{
if(l == ql && r == qr)
{
settag(id, val);
}
else
{
int mid = l + r >> 1;
pushdown(id);
if(qr <= mid) modify(id * 2, l, mid, ql, qr, val);
else if(ql > mid) modify(id * 2 + 1, mid + 1, r, ql, qr, val);
else
{
modify(id * 2, l, mid, ql, mid, val);
modify(id * 2 + 1, mid + 1, r, mid + 1, qr, val);
}
update(id);
}
}
int Query(int id,int l,int r,int ql,int qr)
{
//cout << l << " " << r << " " << ql << " " << qr << endl;
if(l == ql && r == qr)
{
return tr[id].tot;
}
else
{
int mid = l + r >> 1;
pushdown(id);
if(qr <= mid) return Query(id * 2, l, mid, ql, qr);
else if(ql > mid) return Query(id * 2 + 1, mid + 1, r, ql, qr);
else
{
return Query(id * 2, l, mid, ql, mid) + Query(id * 2 + 1, mid + 1, r, mid + 1, qr);
}
update(id);
}
}
int main()
{
int d;
scanf("%d %d", &n, &d);
for(int i = 1; i <= n; i ++)
{
int w;
scanf("%d" ,&w);
if(st[w])
{
modify(1, 1, N - 5, max(w - d, 1), w, -1);
change(1, 1, N - 5, w, 0, 0);
}
else
{
modify(1, 1, N - 5, max(w - d, 1), w, 1);
ll res = Query(1, 1, N - 5, w, min(N - 5, w + d));
change(1, 1, N - 5, w, res, 1);
}
st[w] ^= 1;
printf("%lld\n", tr[1].ans);
}
return 0;
}
评价
我认为这场比赛作为教育场十分具有教育意义。A,B题考查贪心,C考察二分,D考察经典模型,E题考察编辑距离为模型的动态规划问题。F则是线段树维护区间和的应用。可以说每道题都非常有意义,巩固了基础知识和数据结构,还训练了动态规划问题。