杭电多校第四场7月29日补题记录

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 才能收敛。只需要判断字符串中是否出现 19即可,出现即为 NO

D Display Substring

题意:给定一个字符串 S S S,长度为 n n n n ≤ 1 × 1 0 5 n \leq 1\times 10^5 n1×105),并给出每个字母的权值 w i w_i wi,且 1 ≤ w i ≤ 100 1 \leq w_i \leq 100 1wi100。子串权值为子串内各字母的权值和,求第 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,m1×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(ip+1)(qi+1)。从这里可以延申出一个莫队算法:维护 ∑ a i ( i − max ⁡ ( p , L ) + 1 ) \sum a_i(i-\max (p,L)+1) ai(imax(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 n1×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,k1×105

解法:由于雷较少,因而可以考虑根据雷的来统计。

分列处理。首先将这一列有的雷存储下来,若有 x x x 个雷,则将该列分成了 x + 1 x+1 x+1 个子块。显然,这 x + 1 x+1 x+1 个子块之间不能互相到达,只能从上一列承接或者同块内移动。

接下来考虑一个块内如何统计。首先先无条件的继承上一列中同一区块内可以到达的点,然后找到这个区间内最靠上的点,块内其下面的点都可以通过这个点到达。

因而,我们需要一种数据结构:

  1. 区间修改。需要将区间整体刷成 1或者 0表示可以到达或者不可到达。
  2. 区间求和。需要统计 [ 1 , n ] [1,n] [1,n] 区间中 1的数目以表示这一列对答案的贡献。
  3. 区间查询。需要查询区间中最靠上的 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值