A Calculus
题意:从 C , C x , C sin x , C cos x , C x , C sin x , C cos x , C e x \displaystyle C,Cx,C\sin x,C \cos x,\frac{C}{x},\frac{C}{\sin x},\frac{C}{\cos x},C e^x C,Cx,Csinx,Ccosx,xC,sinxC,cosxC,Cex 中组合出一些函数,问其和函数在 x → ∞ x \to \infty x→∞ 时是否收敛。 C ∈ [ 0 , 1 × 1 0 9 ] C \in [0,1\times 10^9] C∈[0,1×109]。
解法:显然这些初等函数的和函数均不收敛,因而
C
=
0
C=0
C=0 才能收敛。只需要判断字符串中是否出现 1
到 9
即可,出现即为 NO
。
D Display Substring
题意:给定一个字符串 S S S,长度为 n n n( n ≤ 1 × 1 0 5 n \leq 1\times 10^5 n≤1×105),并给出每个字母的权值 w i w_i wi,且 1 ≤ w i ≤ 100 1 \leq w_i \leq 100 1≤wi≤100。子串权值为子串内各字母的权值和,求第 k k k 小的子串权值。
解法:第 k k k 小的经典做法——二分答案。二分出这样的权值 x x x,然后去找有多少个子串权值小于 x x x,然后根据数目来改变二分范围。
全部的子串即为全部后缀的全部前缀。因而考虑将全部子串按照后缀分成 n n n 组,然后在每一组内统计个数。注意到权值全为正,因而在同一组内,长度越长的子串权值越大,因而可以继续二分答案,二分出个数。
接下来就是去重——根据样例,本质相同的子串要去掉。这里就是一个非常常见的操作——使用 h e i g h t \rm height height 数组来统计。由于我们现在按照后缀在分组,并且 h e i g h t \rm height height 数组统计了后缀数组序上相邻两个后缀的最长公共前缀长度即 l c p \rm lcp lcp,因而减去即可。
总体复杂度 O ( n log n log M ) O(n \log n \log M) O(nlognlogM)。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <memory.h>
using namespace std;
char a[200005];
int sa[200005], rk[200005], oldsa[200005], oldrk[200005], cnt[200005];
int height[200005], sum[200005];
int value[35], n;
long long k;
bool check(int ask)
{
long long num = 0;
int last = 0;
for (int i = 1; i <= n;i++)//分组统计
{
if (sum[sa[i]] - sum[sa[i] - 1] > ask)
{
last = 0;
continue;
}
int left = sa[i], right = n, place = 0;
while(left<=right)
{
int mid = (left + right) >> 1;
if (sum[mid] - sum[sa[i] - 1] <= ask)
{
place = mid;
left = mid + 1;
}
else
right = mid - 1;
}
num += max(place - sa[i] + 1 - min(last, height[i]), 0);
//在满足条件的范围下,需要去掉的重复子串个数要将height与last取min
last = place - sa[i] + 1;//last为当前组中有多少个前缀可行
}
return num >= k;
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
scanf("%d%lld", &n, &k);
scanf("%s", a + 1);
for (int i = 1; i <= 26; i++)
scanf("%d", &value[i]);
for (int i = 1; i <= n;i++)
sum[i] = sum[i - 1] + value[a[i] - 96];
int len = n;
for (int i = 1; i <= 2 * n; i++)
cnt[i] = 0;
//后缀数组排序
for (int i = 1; i <= len; i++)
{
rk[i] = a[i];
cnt[rk[i]]++;
}
for (int i = 1; i <= max(len, 300); i++)
cnt[i] += cnt[i - 1];
for (int i = len; i >= 1; i--)
{
sa[cnt[rk[i]]] = i;
cnt[rk[i]]--;
}
for (int gap = 1; gap <= len; gap <<= 1)
{
memset(cnt, 0, sizeof(cnt));
memcpy(oldsa, sa, sizeof(sa));
for (int i = 1; i <= len; i++)
cnt[rk[oldsa[i] + gap]]++;
for (int i = 1; i <= max(len, 300); i++)
cnt[i] += cnt[i - 1];
for (int i = len; i >= 1; i--)
{
sa[cnt[rk[oldsa[i] + gap]]] = oldsa[i];
cnt[rk[oldsa[i] + gap]]--;
}
memset(cnt, 0, sizeof(cnt));
memcpy(oldsa, sa, sizeof(sa));
for (int i = 1; i <= len; i++)
cnt[rk[oldsa[i]]]++;
for (int i = 1; i <= max(len, 300); i++)
cnt[i] += cnt[i - 1];
for (int i = len; i >= 1; i--)
{
sa[cnt[rk[oldsa[i]]]] = oldsa[i];
cnt[rk[oldsa[i]]]--;
}
memcpy(oldrk, rk, sizeof(rk));
int place = 0;
for (int i = 1; i <= len; i++)
{
if (oldrk[sa[i]] == oldrk[sa[i - 1]] && oldrk[sa[i] + gap] == oldrk[sa[i - 1] + gap])
rk[sa[i]] = place;
else
rk[sa[i]] = ++place;
}
}
//求height数组
for (int i = 1, place = 0; i <= n; i++)
{
if (place)
place--;
while (a[i + place] == a[sa[rk[i] - 1] + place] && i+place<=n)
place++;
height[rk[i]] = place;
}
long long tot = (long long)n * (n + 1) / 2;//本质不同子串统计
for (int i = 1; i <= n; i++)
tot -= height[i];
if (k > tot)
{
printf("-1\n");
continue;
}
int left = 0, right = 99999999, ans = 0;
while (left <= right)
{
int mid = (left + right) >> 1;
if (check(mid))
{
ans = mid;
right = mid - 1;
}
else
left = mid + 1;
}
printf("%d\n", ans);
}
return 0;
}
E Didn’t I Say to Make My Abilities Average in the Next Life?!
题意:给定一个长度为 n n n 的序列 { a n } \{ a_n \} {an},进行 m m m 次询问,每次给出区间 [ x , y ] [x,y] [x,y],问在其中随意选取区间 [ i , j ] [i,j] [i,j],其期望的最大值与最小值的平均数。 n , m ≤ 1 × 1 0 5 n,m \leq 1\times 10^5 n,m≤1×105。
解法:容易注意到只用分别求出最大值和最小值乘以其出现次数即可,这两个问题本质等价。因而下面只考虑最大值的贡献情况。
考虑离线询问。将右端点为 r r r 的询问放在一起,然后从左到右依次加入一个元素,去查以当前元素作为右端点对应的答案。
首先,记第 i i i 个元素左边第一个比它大的元素下标为 p p p,右侧比它大的元素下标为 q q q,则它对一个足够大的区间的贡献为 a i ( i − p + 1 ) ( q − i + 1 ) a_i(i-p+1)(q-i+1) ai(i−p+1)(q−i+1)。从这里可以延申出一个莫队算法:维护 ∑ a i ( i − max ( p , L ) + 1 ) \sum a_i(i-\max (p,L)+1) ∑ai(i−max(p,L)+1) 与 ∑ a i ( min ( q , R ) − i + 1 ) \sum a_i(\min (q,R)- i+1) ∑ai(min(q,R)−i+1),对于待求区间 [ L , R ] [L,R] [L,R] 的移动这两个值会相应的变化。可以用前缀和维护这个东西,进行单调栈的压入和回滚操作。时间复杂度 O ( n n ) O(n \sqrt{n}) O(nn)。
此外可以考虑右移一个位置对左边每一个端点的影响。考虑维护一个差分数组,第 i i i 位表示当前区间 [ i , R ] [i,R] [i,R] 上最大值对答案的贡献,即最大值乘以对应的次数,因而求和就可以求出对应的区间答案。
执行范围向右移动一位之后,会新增出 R R R 个区间。对于新增区间,其右端点固定为 R R R,因而这些区间的最大值满足单调栈的关系。用单调栈去维护,那么可能会涉及到弹出元素的问题。这些被弹出的元素也有对应成为最大值的区间,但是它们不再呈现在新的最大值统计(即每一次右移的增量统计)中了,因为它已经被现在来的这个值给替代了。将它们加入到历史版本的最大值统计中,同时该区间(以新的右端点)的最大值被修改成了当前的值。
当我们向单调栈中插入当前元素的时候,意味着维护工作结束,要进行统计工作。注意到第 i i i 位上我们都有当前区间 [ i , R ] [i,R] [i,R] 的的最大值,现在要统计次数。显然这个次数呈现了一个等差数列,因而又是差分。然后对差分数组求和即可。此处这个差分数组的含义刚好和区间 [ i , R ] [i,R] [i,R] 最大值贡献暗合。
总体复杂度 O ( n log n ) O(n \log n) O(nlogn)。
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const long long mod = 1000000007ll;
long long power(long long a,long long x)
{
long long ans = 1;
while(x)
{
if(x&1)
ans = a * ans % mod;
a = a * a % mod;
x >>= 1;
}
return ans;
}
long long inv(long long x)
{
return power(x, mod - 2);
}
struct node1
{
long long sum;
long long tag;
};
struct node2
{
long long sumA;
long long sumB;
long long sum;
long long tagA;
long long tagB;
};
struct segtreeA
{
vector<node1> tree;
void pushdown(int place,int left,int right)
{
if(tree[place].tag)
{
int mid = (left + right) >> 1;
update(place << 1, left, mid, left, mid, tree[place].tag);
update(place << 1 | 1, mid + 1, right, mid + 1, right, tree[place].tag);
tree[place].tag = 0;
}
return;
}
void update(int place,int left,int right,int start,int end,long long x)
{
if(start<=left && right<=end)
{
tree[place].sum = (tree[place].sum + (right - left + 1) * x % mod) % mod;
tree[place].tag = (tree[place].tag + x) % mod;
return;
}
pushdown(place, left, right);
int mid = (left + right) >> 1;
if(start<=mid)
update(place << 1, left, mid, start, end, x);
if(end>mid)
update(place << 1 | 1, mid + 1, right, start, end, x);
tree[place].sum = (tree[place << 1].sum + tree[place << 1 | 1].sum) % mod;
return;
}
long long query(int place,int left,int right,int start,int end)
{
if(start<=left && right<=end)
return tree[place].sum;
pushdown(place, left, right);
int mid = (left + right) >> 1;
long long ans = 0;
if(start<=mid)
ans = (ans + query(place << 1, left, mid, start, end)) % mod;
if(end>mid)
ans = (ans + query(place << 1 | 1, mid + 1, right, start, end)) % mod;
return ans;
}
};
struct segtreeB
{
vector<node2> tree;
void pushdown(int place,int left,int right)
{
int mid = (left + right) >> 1;
if(tree[place].tagA)
{
set(place << 1, left, mid, left, mid, tree[place].tagA);
set(place << 1 | 1, mid + 1, right, mid + 1, right, tree[place].tagA);
tree[place].tagA = 0;
}
if(tree[place].tagB)
{
add(place << 1, left, mid, left, mid, tree[place].tagB);
add(place << 1 | 1, mid + 1, right, mid + 1, right, tree[place].tagB);
tree[place].tagB = 0;
}
return;
}
void set(int place,int left,int right,int start,int end,long long x)
{
if(start<=left && right<=end)
{
tree[place].sumA = (right - left + 1) * x % mod;
tree[place].sum = tree[place].sumB * x % mod;
tree[place].tagA = x;
return;
}
pushdown(place, left, right);
int mid = (left + right) >> 1;
if(start<=mid)
set(place << 1, left, mid, start, end, x);
if(end>mid)
set(place << 1 | 1, mid + 1, right, start, end, x);
tree[place].sumA = (tree[place << 1].sumA + tree[place << 1 | 1].sumA) % mod;
tree[place].sumB = (tree[place << 1].sumB + tree[place << 1 | 1].sumB) % mod;
tree[place].sum = (tree[place << 1].sum + tree[place << 1 | 1].sum) % mod;
return;
}
void add(int place,int left,int right,int start,int end,long long x)
{
if(start<=left && right<=end)
{
tree[place].sum = (tree[place].sum + tree[place].sumA * x % mod) % mod;
tree[place].sumB = (tree[place].sumB + (right - left + 1) * x % mod) % mod;
tree[place].tagB = (tree[place].tagB + x) % mod;
return;
}
pushdown(place, left, right);
int mid = (left + right) >> 1;
if(start<=mid)
add(place << 1, left, mid, start, end, x);
if(end>mid)
add(place << 1 | 1, mid + 1, right, start, end, x);
tree[place].sumA = (tree[place << 1].sumA + tree[place << 1 | 1].sumA) % mod;
tree[place].sumB = (tree[place << 1].sumB + tree[place << 1 | 1].sumB) % mod;
tree[place].sum = (tree[place << 1].sum + tree[place << 1 | 1].sum) % mod;
return;
}
long long query(int place,int left,int right,int start,int end)
{
if(start<=left && right<=end)
return tree[place].sum;
pushdown(place, left, right);
int mid = (left + right) >> 1;
long long ans = 0;
if(start<=mid)
ans = (ans + query(place << 1, left, mid, start, end)) % mod;
if(end>mid)
ans = (ans + query(place << 1 | 1, mid + 1, right, start, end)) % mod;
return ans;
}
};
vector<pair<int, int>> ask[200005];
long long ans[200005], len[200005], a[200005];
int n, st[200005];
void solve(int op)
{
segtreeA left;
left.tree.resize(4 * n + 3);
segtreeB right;
right.tree.resize(4 * n + 3);
int size = 0;
for (int i = 1; i <= n;i++)
{
int nowA = op < 0 ? -a[i] : a[i];
while (size && a[i] >= a[st[size]])
{
int nowB = op < 0 ? -a[st[i]] : a[st[i]];
left.update(1, 1, n, st[size - 1] + 1, st[size], nowB * (i - st[size]) % mod);
right.add(1, 1, n, st[size - 1] + 1, st[size], st[size] - i + mod);
size--;
}
right.set(1, 1, n, st[size] + 1, i, nowA);
right.add(1, 1, n, 1, i, 1);
st[++size] = i;
for (auto j : ask[i])
{
ans[j.second] = (ans[j.second] + left.query(1, 1, n, j.first, i)) % mod;
ans[j.second] = (ans[j.second] + right.query(1, 1, n, j.first, i)) % mod;
}
}
}
int main()
{
int t, m;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n;i++)
ask[i].clear();
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
for (int i = 1; i <= m; i++)
ans[i] = 0;
for (int i = 1; i <= m; i++)
{
int l, r;
scanf("%d%d", &l, &r);
ask[r].push_back(make_pair(l, i));
long long length = r - l + 1;
len[i] = inv(length * (length + 1) % mod);
}
solve(1);
for (int i = 1; i <= n;i++)
a[i] = -a[i];
solve(-1);
for (int i = 1; i <= m; i++)
printf("%lld\n", ans[i] * len[i] % mod);
}
return 0;
}
G Increasing Subsequence
题意:给定一个长度为 n n n 的序列 { a n } \{ a_n \} {an},问极大上升子序列个数。 n ≤ 1 × 1 0 5 n \leq 1\times 10^5 n≤1×105。
解法:首先构造一个 O ( n 2 ) O(n^2) O(n2) 的 DP 算法:记 f i f_i fi 为以第 i i i 个点为终止的极大上升子序列数目。则它可以从一些满足 a j < a i a_j<a_i aj<ai 的 f j f_j fj 处转移,只要 [ i , j ] [i,j] [i,j] 中再无 [ a i , a j ] [a_i,a_j] [ai,aj] 中元素。如果当前前面的元素都比 a i a_i ai 大,则 f i = 1 f_i=1 fi=1。因而可以写出这样的 DP 代码:
for(int i=1;i<=n;i++)
{
int maximum=0;
for(int j=i-1;j>=1;j--)
{
if(a[j]>=a[i] || a[j]<maximum)
continue;
f[i]+=f[j];
maximum=a[j];
}
if(!maximum)
f[i]=1;
}
通过这个 DP,我们不难发现,当前转移到 f i f_i fi 的候选集合满足单调性——即随着下标增大而 a j a_j aj 减小。这与单调栈非常类似,因而考虑使用单调栈来维护转移。
可是这里朴素的遍历也只有 O ( n ) O(n) O(n) 的复杂度,加入单调栈并不会影响单个的遍历与转移,因而考虑使用单调栈维护后面一些值的转移。
本题采用分治的方法。将整个区间 [ l , r ] [l,r] [l,r] 划成 [ l , m i d ] [l,mid] [l,mid] 与 [ m i d + 1 , r ] [mid+1,r] [mid+1,r],先处理左侧区域,然后考虑左右合并,最后再处理右侧。这是因为,右侧的 DP 转移依赖于左侧转移的结果。
考虑合并过程。首先按照 a i a_i ai 值进行插入。如果当前这个元素在左侧,直接插入左侧的单调栈。这个单调栈是维护的待转移集合,需要保证栈中元素下标依次单减。
对于右侧的元素,它们需要考虑哪些能从左侧转移而来的,右侧的内部转移是由右侧的分治解决的。显然,左侧能转移到当前 a i a_i ai 的元素 j j j 的 a j a_j aj 必须比右侧除了当前的第 i i i 位之外最大的 a x a_x ax 还要大,否则就被 x x x 截胡了;但是也不能比当前的 a i a_i ai 还要大,否则就无法承接。因而,对于右侧元素,也需要维护一个单调栈——它需要下标单增。因而可以转移的区间就是在 a x a_x ax 与 a i a_i ai 范围内全部的左侧栈中元素对应的 f i f_i fi。这就是一个区间求和问题,而且不断的插入最后一个元素(注意我们是按照值单增在插入),因而维护一个前缀和即可 O ( 1 ) O(1) O(1) 插入 O ( 1 ) O(1) O(1) 查询。
整体复杂度 O ( n log 2 n ) O(n \log^2 n) O(nlog2n)。
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const long long mod = 998244353LL;
long long f[200005];
int a[200005];
void solve(int left,int right)
{
if(left==right)
return;
int mid = (left + right) >> 1;
solve(left, mid);
vector<long long> sum;
sum.push_back(0);
vector<int> left_stack, right_stack;
vector<pair<int, int>> place;
for (int i = left; i <= right;i++)
place.push_back(make_pair(a[i], i));
sort(place.begin(), place.end());
for (auto i : place)
{
if(i.second<=mid)
{
while (!left_stack.empty() && left_stack.back() < i.second)
//左侧栈保证下标在单减:值越大越靠前
{
sum.pop_back();
left_stack.pop_back();
}
left_stack.push_back(i.second);
sum.push_back((sum.back() + f[i.second]) % mod);
//直接在最后插入值(因为当前的数列值是左侧栈中最大的),更新前缀和。
}
else
{
while (!right_stack.empty() && right_stack.back() > i.second)
//右侧栈值越大越靠后
right_stack.pop_back();
if(left_stack.empty())
continue;
int id1 = partition_point(left_stack.begin(), left_stack.end(), [&](int x) { return a[x] < a[i.second]; }) - left_stack.begin();
//可以使用更加复杂的lower_bound实现
int id2 = right_stack.empty() ? 0 : partition_point(left_stack.begin(), left_stack.end(), [&](int x) { return a[x] < a[right_stack.back()]; }) - left_stack.begin();
f[i.second] = (f[i.second] + (sum[id1] - sum[id2]) % mod + mod) % mod;
right_stack.push_back(i.second);
}
}
solve(mid + 1, right);
}
int main()
{
int n, t;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int minimum = n + 1;
for (int i = 1; i <= n;i++)
{
if(a[i]<minimum)
{
f[i] = 1;//初始化
minimum = a[i];
}
else
f[i] = 0;
}
solve(1, n);
long long ans = 0;
int maximum = 0;
for (int i = n; i >= 1;i--)
if(a[i]>maximum)//出于简化,可以在数列的最后加一个最大值如n+1简化代码
{
ans = (ans + f[i]) % mod;
maximum = a[i];
}
printf("%lld\n", ans);
}
return 0;
}
H Lawn of the Dead
题意:给定一个 n × m n \times m n×m 的棋盘,棋盘中有 k k k 个雷,一个僵尸只能从 ( i , j ) (i,j) (i,j) 移动到 ( i + 1 , j ) (i+1,j) (i+1,j) 或者 ( i , j + 1 ) (i,j+1) (i,j+1),问棋盘中有多少点能走到。 n , m , k ≤ 1 × 1 0 5 n,m,k \leq 1\times 10^5 n,m,k≤1×105。
解法:由于雷较少,因而可以考虑根据雷的来统计。
分列处理。首先将这一列有的雷存储下来,若有 x x x 个雷,则将该列分成了 x + 1 x+1 x+1 个子块。显然,这 x + 1 x+1 x+1 个子块之间不能互相到达,只能从上一列承接或者同块内移动。
接下来考虑一个块内如何统计。首先先无条件的继承上一列中同一区块内可以到达的点,然后找到这个区间内最靠上的点,块内其下面的点都可以通过这个点到达。
因而,我们需要一种数据结构:
- 区间修改。需要将区间整体刷成
1
或者0
表示可以到达或者不可到达。 - 区间求和。需要统计
[
1
,
n
]
[1,n]
[1,n] 区间中
1
的数目以表示这一列对答案的贡献。 - 区间查询。需要查询区间中最靠上的
1
的位置。
综和以上几个条件,我们发现线段树是最合适的。
整体复杂度 O ( k log n + n log n ) O(k \log n+n \log n) O(klogn+nlogn),常数略大。
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
struct node
{
long long sum;
int first;//区间最靠上的1的位置,初始全为n+1
int tag;
};
struct node t[400005];
vector<int> mine[100005];
int n, m;
void build(int place,int left,int right)
{
t[place].sum = 0;
t[place].tag = -1;
t[place].first = n + 1;
if(left==right)
return;
int mid = (left + right) >> 1;
build(place << 1, left, mid);
build(place << 1 | 1, mid + 1, right);
}
void update(int place, int left, int right, int start, int end, int x);
void pushdown(int place,int left,int right)
{
if(t[place].tag!=-1)
{
int mid = (left + right) >> 1;
update(place << 1, left, mid, left, mid, t[place].tag);
update(place << 1 | 1, mid + 1, right, mid + 1, right, t[place].tag);
t[place].tag = -1;
}
return;
}
void update(int place,int left,int right,int start,int end,int x)
{
if(start<=left && right<=end)
{
t[place].sum = (right - left + 1) * x;
if(x)
t[place].first = left;
else
t[place].first = n + 1;
t[place].tag = x;
return;
}
pushdown(place, left, right);
int mid = (left + right) >> 1;
if(start<=mid)
update(place << 1, left, mid, start, end, x);
if(end>mid)
update(place << 1 | 1, mid + 1, right, start, end, x);
t[place].sum = t[place << 1].sum + t[place << 1 | 1].sum;
t[place].first = min(t[place << 1].first, t[place << 1 | 1].first);
}
long long query_first(int place,int left,int right,int start,int end)
{
if(start<=left && right<=end)
return t[place].first;
pushdown(place, left, right);
int mid = (left + right) >> 1;
long long ans = n + 1;
if(start<=mid)
ans = min(ans, query_first(place << 1, left, mid, start, end));
if(end>mid)
ans = min(ans, query_first(place << 1 | 1, mid + 1, right, start, end));
return ans;
}
int query_sum(int place,int left,int right,int start,int end)
{
if(start<=left && right<=end)
return t[place].sum;
pushdown(place, left, right);
int mid = (left + right) >> 1;
int ans = 0;
if(start<=mid)
ans += query_sum(place << 1, left, mid, start, end);
if(end>mid)
ans += query_sum(place << 1 | 1, mid + 1, right, start, end);
return ans;
}
int main()
{
int t, k, x, y;
scanf("%d", &t);
while(t--)
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= m;i++)
mine[i].clear();
build(1, 1, n);
for (int i = 1; i <= k;i++)
{
scanf("%d%d", &x, &y);
mine[y].push_back(x);
}
for (int i = 1; i <= m;i++)
{
mine[i].push_back(0);
mine[i].push_back(n + 1);
sort(mine[i].begin(), mine[i].end());//添加两个哨兵,使得每一列都处在若干个区间之中
}
long long ans = 0;
for(auto i:mine[1])//第一列特殊处理
if(i>1)
{
update(1, 1, n, 1, i - 1, 1);
ans += query_sum(1, 1, n, 1, n);
break;
}
for (int i = 2; i <= m;i++)
{
for (auto j : mine[i])
if (j >= 1 && j <= n)
update(1, 1, n, j, j, 0);
for (int j = 0; j + 1 < mine[i].size();j++)
{
int left = mine[i][j] + 1, right = mine[i][j + 1] - 1;
if(left>right)
continue;
int first = query_first(1, 1, n, left, right);
update(1, 1, n, left, right, 0);
if(first<=right)
update(1, 1, n, first, right, 1);
}
ans += query_sum(1, 1, n, 1, n);
}
printf("%lld\n", ans);
}
return 0;
}