IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> 杭电多校(MINIEYE)第四场 补题 -> 正文阅读

[开发测试]杭电多校(MINIEYE)第四场 补题

1004 Display Substring

题目

问题描述
当我们在LED屏幕上显示一个字符串时,显示不同的字符可能会有不同的能耗。给定一个长度为 n 的字符串 S,由小写英文字母和每个字母的能量消耗组成。假设一个字符串的能量消耗是它所有字符能量消耗的总和。请找出 S 的所有不同子串的第 k 个最小能量消耗。

请注意,字符串 S 的子串是 S 的连续子序列。如果两个字符串 S 和 T 的长度不同,或者至少存在一个满足 S[i]≠T[i] 的位置 i,我们就说这两个字符串 S 和 T 不同。

输入
输入的第一行包含一个整数T(1≤T≤2×103)—测试用例的数量。

每个测试用例的第一行包含两个整数 n (1≤n≤105) 和 k (1≤k≤n(n+1)2)。

每个测试用例的第二行包含一个由小写英文字母组成的长度为 n 的字符串 S。

每个测试用例的第三行包含26个整数ca,cb,…,cz (1≤cα≤100)—每个字母的能量消耗。

保证所有测试用例中n的总和不超过8×105。

输出
对于每个测试用例,在单独的行中输出一个整数 — 第 k 最小的能量消耗,如果没有答案,则为 -1。

样本输入

2
5 5
ababc
3 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
5 15
ababc
3 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

样本输出

4
-1

解题思路

二分答案,然后使用任意一种后缀数据结构 check 即可。

如何 check?

后缀数组:所有后缀的所有前缀即所有子串。我们遍历后缀数组,对于每个后缀,其越长的前缀能耗越大,于是可以二分找到能耗小于等于要 check 的值的前缀的个数,再减去重复部分即可。而每个后缀被重复统计的部分就是 height 数组对应的值。

后缀自动机/后缀树:这两种后缀数据结构都是将本质不同的子串记录在其结点上。每个结点表示的子串都是形如Suffix(T,i)+S的串(即取 的一个后缀和 连接构成的串,在后缀树上则是

再翻转一次的串)。显然我们取的后缀越长,其表示的子串能耗越大,于是可以二分这个长

度来找到满足条件的子串的个数,每次 check 对每个结点做一次二分即可。

时间复杂度O(n㏒n㏒k)。

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1000000 + 9;
const int M = 998244353;
long long f[N], _S[N];

void Pre() {
    long long pow2 = 1;
    for (int i = 1; i < N; ++i) {
        pow2 = pow2 * 2 % M;
        f[i] = (pow2 - f[i] + M) % M;
        _S[i] = (f[i] + _S[i - 1]) % M;
        for (int j = i + i; j < N; j += i) {
            f[j] = (f[j] + f[i]) % M;
        }
    }
}

long long Pow2(long long n) {
    long long r = 1, x = 2;
    for (; n; n >>= 1) {
        if (n & 1) r = r * x % M;
        x = x * x % M;
    }
    return r;
}

struct Du {
    int tot, sqrtn;
    long long n, S[N * 2];
    int id(long long x) { return x <= sqrtn ? x : tot - n / x; }
    void Pre(long long _n) {
        tot = 1; n = _n; sqrtn = sqrt(n + 0.5);
        for (long long l = 1, r; l <= n; l = r + 1) {
            r = n / (n / l);
            S[tot++] = r < N ? _S[r] : -1;
        }
    }
    long long Calc_S(long long n) {
        if (~S[id(n)]) return S[id(n)];
        long long s = 0;
        for (long long l = 2, r; l <= n; l = r + 1) {
            r = n / (n / l);
            s += (r - l + 1) % M * Calc_S(n / l) % M;
        }
        s = ((Pow2(n + 1) - 2 - s) % M + M) % M;
        return S[id(n)] = s;
    }
    long long Calc_Ans(long long n) {
        Pre(n); Calc_S(n);
        long long ans = Pow2(n);
        for (long long l = 1, r; l <= n / 2; l = r + 1) {
            r = min(n / (n / l), n / 2);
            ans += (n / l - 1) * (S[id(r)] - S[id(l - 1)]) % M;
        }
        return (ans % M + M) % M;
    }
} du;

void solve() {
    long long n;
    scanf("%lld", &n);
    printf("%lld\n", du.Calc_Ans(n));
}

int main() {
    Pre();
    int T; scanf("%d", &T);
    while (T--) solve();
    return 0;
}

1007 Increasing Subsequence

题目

问题描述
在整数序列 a1,a2,…,an 中,如果一个递增子序列不是其他递增子序列的子序列,我们称其为极大。子序列是我们可以通过从原始序列中删除一些(可能为零)元素来获得的序列。

寻找或计算最长递增子序列是一个经典问题。现在 Yukikaze 想让你计算一些以 998244353 为模的排列中最大递增子序列的数量。长度为 n 的排列是一个数字序列,这样从 1 到 n 的每个数字都只出现一次。

输入
输入的第一行包含一个整数 T (1≤T≤104),表示测试用例的数量。

每个测试用例的第一行包含一个整数 n (1≤n≤105),表示排列的长度。

每个测试用例的第二行包含 n 个整数 a1,a2,…,an (1≤ai≤n),表示排列。保证从 1 到 n 的每个数字都只出现一次。

所有测试用例中 n 的总和不会超过 2×105。

输出
对于每个测试用例,输出一个整数,表示给定置换模 998244353 中最大递增子序列的数量。

样本输入

2
4
2 1 4 3
5
1 5 2 4 3

样本输出

4
3

解题思路

定义 dp[i] 为 a[1…i] 中以 a[i] 为结尾的极长上升子序列个数。则 dp[i] 对 dp[j] 有贡献当且仅当

i 到 j 之间没有 a[i] 到 a[j] 之间的元素。 dp[i] 初值为 1 当且仅当 a[i] 前面没有比 a[i] 小的。

dp[i] 对答案有贡献当且仅当 a[i] 后面没有比 a[i] 大的。这样就有了一个朴素的 做法。

通过分治并对两侧分别维护单调栈,我们可以把时间复杂度优化到O(n*㏒?n)。

当处理区间 [l,r] 时,令 m 为 [l,r] 的中点,对 a[l,r] 中所有元素按值依次从小到大处理。对 [l,m] 和 [m+1,r] 分别建单调栈。左边单调栈中的元素值从小到大,位置从大到小。右边的单调栈中元素值从小到大,位置从小到大。处理左边的元素时直接往单调栈里丢,处理右边的元素 a[i] 时通过单调栈找到 [m+1,i-1] 中比他小的最靠右侧的元素 a[j] 。然后在左侧的单调栈中找到比 a[j] 大的第一个元素和比 a[i] 小的最后一个元素。区间 [l,m] 对 dp[i] 的贡献就来自单调栈里这两个元素之间的元素的 dp 值之和。

代码

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

typedef pair<int, int> pii;

const int P = 998244353;
int add(int a, int b) { a += b; return a < P ? a : a - P; }
int sub(int a, int b) { a -= b; return a < 0 ? a + P : a; }

const int N = 100001;
int a[N], dp[N];

void solve(int l, int r) {
    if (l + 1 == r) return;
    int mid = (l + r) >> 1;
    solve(l, mid);
    vector<int> pos(r - l);
    iota(pos.begin(), pos.end(), l);
    sort(pos.begin(), pos.end(), [&](int i, int j) { return a[i] < a[j]; });
    vector<int> ls, rs;
    vector<int> sum(1, 0);
    for (int i : pos) {
        if (i < mid) {
            while (!ls.empty() && ls.back() < i)
                sum.pop_back(), ls.pop_back();
            ls.push_back(i);
            sum.push_back(add(sum.back(), dp[i]));            
        }
        else {
            while (!rs.empty() && rs.back() > i)
                rs.pop_back();
            if (ls.empty()) continue;
            int id1 = partition_point(ls.begin(), ls.end(), [&](int x) { return a[x] < a[i]; }) - ls.begin();
            int id2 = rs.empty() ? 0 : partition_point(ls.begin(), ls.end(), [&](int x) { return a[x] < a[rs.back()]; }) - ls.begin();
            dp[i] = add(dp[i], sub(sum[id1], sum[id2]));
            rs.push_back(i);
        }
    }
    solve(mid, r);
}

int main(void) {
    int T; scanf("%d", &T);
    while (T--) {
        int n; scanf("%d", &n);
        for (int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        for (int i = 1, v = INT_MAX; i <= n; ++i) {
            if (v > a[i])
                dp[i] = 1;
            else 
                dp[i] = 0;
            v = min(v, a[i]);
        }
        solve(1, n + 1);
        int ans = 0;
        for (int i = n, v = 0; i >= 1; --i) {
            if (v < a[i])
                ans = add(ans, dp[i]);
            v = max(v, a[i]);
        }
        printf("%d\n", ans);
    }
    return 0;
}

1008 Lawn of the Dead

题目

问题描述
有一天,一只丧尸来到了死者草坪,它可以被看作是一个 n×m 的网格。最初,他站在左上角的单元格上,即 (1,1)。

因为丧尸的脑子坏掉了,所以他没有很好的方向感。他只能一步从(i,j) 向下移动到(i+1,j) 或从(i,j) 向右移动到(i,j+1)。

网格上有 k 个“lotato 地雷”。第 i 个矿在 (xi,yi)。万一被摧毁,他绝不会踏入装有“洛托地雷”的牢房。

那么他能达到多少细胞呢? (包括起始单元格)

输入
第一行包含一个整数 t (1≤t≤20),表示测试用例的数量。

每个测试用例的第一行包含三个整数 n,m,k (2≤n,m,k≤105) — 有一个 n×m 网格,网格上有 k 个“lotato mines”。

以下 k 行中的每一行都包含 2 个整数 xi,yi (1≤xi≤n,1≤yi≤m) — 在 (xi,yi) 处有一个“lotato 地雷”。可以保证在 (1,1) 处没有“lotato 地雷”,并且在同一个单元格中没有地雷。

保证∑n≤7?105,∑m≤7?105。

输出
对于每个测试用例,输出他可能到达的单元格数量。

样本输入

1
4 4 4
1 3
3 4
3 2
4 3

样本输出

10

提示

僵尸可能到达的细胞是
(1,1), (1,2), (2,1), (2,2), (2,3), (2,4), (3,1), (3,3), (4,1), (4,2).

解题思路

考虑所有点的个数减去不能到达的点的个数,即为可以到达的点的个数。

根据题意,有地雷的地方是不可以到达的。由于僵尸只会向右和向下走,当某个点的左边和上方都不可达时,该点不可达,并会对自己右边的点和下方的点造成影响。

由于空间很大但地雷数有限,可以从上往下逐行对每一行的地雷排序后进行处理。对每个地雷,找到从自己的右上角点 (x-1,y+1)开始的从左往右的连续不可达区域的范围,那么 这行的这个范围也不可达。可以用线段树来实现区间查询和区间覆盖。每一行处理完后查询该行不可达的点数,累加后用总点数减即得到答案。

代码

#include<bits/stdc++.h>
using namespace std;
#define ls (x<<1)
#define rs (x<<1|1)
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;
vector<int>e[N];
int tr[2][N << 2], lz[2][N << 2];

void push_down(int f, int x, int l, int r, int mid) {
	if (lz[f][x] == -1)return;
	tr[f][ls] = lz[f][x] * (mid - l + 1);
	tr[f][rs] = lz[f][x] * (r - mid);
	lz[f][ls] = lz[f][rs] = lz[f][x];
	lz[f][x] = -1;
}
void update(int f,int x, int l, int r, int L, int R, int v) {
	if (L <= l && R >= r) {
		tr[f][x] = (r - l + 1) * v;
		lz[f][x] = v;
		return;
	}
	int mid = (l + r) >> 1;
	push_down(f, x, l, r, mid);
	if (R <= mid)update(f, ls, l, mid, L, R, v);
	else if (L > mid)update(f, rs, mid + 1, r, L, R, v);
	else {
		update(f, ls, l, mid, L, mid, v);
		update(f, rs, mid + 1, r, mid + 1, R, v);
	}
	tr[f][x] = tr[f][ls] + tr[f][rs];
}
int query(int f, int x, int l, int r, int L, int R) {
	if (!tr[f][x])return inf;
	if (l == r)return l;
	int mid = l + r >> 1;
	push_down(f, x, l, r, mid);
	if (L <= l && R >= r) {
		if (tr[f][ls] > 0) return query(f, ls, l, mid, l, mid);
		else return query(f, rs, mid + 1, r, mid + 1, r);
	}
	else {
		if (R <= mid)return query(f, ls, l, mid, L, R);
		else if (L > mid)return query(f, rs, mid + 1, r, L, R);
		else return min(query(f, ls, l, mid, L, mid), query(f, rs, mid + 1, r, mid + 1, R));
	}
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int n, m, k;
		scanf("%d %d %d", &n, &m, &k);
		for (int i = 1; i <= n; ++i)e[i].clear();
		for (int i = 1; i <= (m << 2); ++i) {
			tr[0][i] = tr[1][i] = 0;
			lz[0][i] = lz[1][i] = -1;
		}
		for (int i = 0; i < k; ++i) {
			int x, y;
			scanf("%d %d", &x, &y);
			e[x].push_back(y);
		}
		long long ans = 0;
		update(0, 1, 1, m, 1, 1, 1);
		for (int x = 1; x <= n; ++x) {
			int l = 0;
			sort(e[x].begin(), e[x].end());
			for (auto& y : e[x]) {
				if (y - 1 >= l + 1) {
					int pos = query((x & 1) ^ 1, 1, 1, m, l + 1, y - 1);
					if (pos != inf)update(x & 1, 1, 1, m, pos, y - 1, 1);
				}
				l = y;
			}
			if (l + 1 <= m) {
				int pos = query((x & 1) ^ 1, 1, 1, m, l + 1, m);
				if (pos != inf)update(x & 1, 1, 1, m, pos, m, 1);
			}
			ans += tr[x & 1][1];
			update((x & 1) ^ 1, 1, 1, m, 1, m, 0);
		}
		printf("%lld\n", ans);
	}
	return 0;
}
  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-08-06 10:07:54  更:2021-08-06 10:09:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 9:07:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码