7.30模考总结
省流:上300了 (模考难度不大,橙黄绿蓝)
\(7.30\) 晴
\(T1\) 报数游戏Ⅱ
题意简述
求在一段序列前加入一个最小的正整数,使这个序列的每一个前缀和都为正数。
思路:
前缀和扫一遍,找最小前缀和,特判为正数的情况,复杂度 \(O(n)\)
\(code\)
#pragma G++ optimize(3, "Ofast", "inline")
#pragma G++ optimize(2)
#include <iostream>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <climits>
#include <random>
#include <bitset>
#include <unordered_map>
#define C(y,x) fac[y]/fac[(y)-(x)]/fac[x]
#define _max(a,b) (a>b?a:b)
#define _min(a,b) (a<b?a:b)
#define orz %
#define ll long long
#define int long long
#define juruo Optimist_Skm
#define mid (l + r >> 1)
#define pii std::pair<int, int>
#define fi first
#define se second
#define eb emplace_back
#define pb push_back
#define m_p std::make_pair
#define pq std::priority_queue<int>
#define pq_min_std::priority_queue<int, std::vector<int>, std::greater<int> >
#define pq_min_pii std::priority_queue<pii, std::vector<pii>, std::greater<pii> >
#define open(x) freopen(#x".in", "r", stdin);freopen(#x".out", "w", stdout);
#define test(x) cout << "Test: " << x << '\n'
#define close fclose(stdin);fclose(stdout);
#define ull unsigned long long
#define debug(); printf("Skmqwq\n");
namespace Fast_Skm {
template <typename T>
inline void read(T &x) {
T s = 0, w = 1;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') w = -1;
c = getchar();
}
while(isdigit(c))
s = (s << 1) + (s << 3) + (c & 0xcf), c = getchar();
x = s * w;
return ;
}
template <typename T, typename... Arp>
inline void read(T &x, Arp &...arp) {
read(x), read(arp...);
return ;
}
template <typename T>
inline void write(T x) {
if(x < 0) x = -x, putchar('-');
int p = 0;
static char s[100];
do {
s[++p] = x orz 10 + '0';
x /= 10;
} while (x);
while(p) putchar(s[p--]);
putchar(' ');
}
template <typename T, typename... Arp>
inline void write(T &x, Arp &...arp) {
write(x), write(arp...);
return ;
}
template <typename S, typename T>
inline void smax(S &x, T y) {
x = (x > y) ? x : y;
}
template <typename S, typename T>
inline void smin(S &x, T y) {
x = (x < y) ? x : y;
}
inline void quit() {
exit(0);
return ;
}
inline ll quick_pow(ll a, ll b, ll P) {
ll ans = 1;
while(b >= 1) {
if(b & 1) {
ans = ans * a % P;
}
a = a * a % P;
b >>= 1;
}
return ans;
}
template <typename T>
inline T exgcd(T a, T b, T &x, T &y) {
if(b == 0) {
x = 1; y = 0;
return a;
}
int gcd = exgcd(b, a % b, x, y);
int tmp = y;
y = x - a / b * y;
x = tmp;
return gcd;
}
} using namespace Fast_Skm;
const int N = 1e5 + 7;
int n, a[N];
ll sum[N];
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
read(n);
for (int i = 1; i <= n; ++i) {
read(a[i]);
}
for (int i = 1; i <= n; ++i) {
sum[i] = sum[i - 1] + a[i];
}
ll ans = sum[1];
for (int i = 2; i <= n; ++i) {
smin(ans, sum[i]);
}
if (ans > 0) {
write(1);
} else {
write(-ans + 1);
}
return 0;
}
本题总结:
要保证签到题的 \(AC\)
\(T2\) 百万富翁的第二次实验
题意简述:
求一段序列里最长的一段区间,使这段区间里的元素互不重复。
思路:
值域是 \(10^{11}\) ,但序列长度只有 \(10^5\) ,所以可以想到离散化,然后就变成一道双指针的模版,直接把左右指针向左做同向运动即可,复杂度 \(O(n)\) 。
\(code\)
#pragma G++ optimize(3, "Ofast", "inline")
#pragma G++ optimize(2)
#include <iostream>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <climits>
#include <random>
#include <bitset>
#include <unordered_map>
#define C(y,x) fac[y]/fac[(y)-(x)]/fac[x]
#define _max(a,b) (a>b?a:b)
#define _min(a,b) (a<b?a:b)
#define orz %
#define ll long long
#define int long long
#define juruo Optimist_Skm
#define mid (l + r >> 1)
#define pii std::pair<int, int>
#define fi first
#define se second
#define eb emplace_back
#define pb push_back
#define m_p std::make_pair
#define pq std::priority_queue<int>
#define pq_min_std::priority_queue<int, std::vector<int>, std::greater<int> >
#define pq_min_pii std::priority_queue<pii, std::vector<pii>, std::greater<pii> >
#define open(x) freopen(#x".in", "r", stdin);freopen(#x".out", "w", stdout);
#define test(x) cout << "Test: " << x << '\n'
#define close fclose(stdin);fclose(stdout);
#define ull unsigned long long
#define debug(); printf("Skmqwq\n");
namespace Fast_Skm {
template <typename T>
inline void read(T &x) {
T s = 0, w = 1;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') w = -1;
c = getchar();
}
while(isdigit(c))
s = (s << 1) + (s << 3) + (c & 0xcf), c = getchar();
x = s * w;
return ;
}
template <typename T, typename... Arp>
inline void read(T &x, Arp &...arp) {
read(x), read(arp...);
return ;
}
template <typename T>
inline void write(T x) {
if(x < 0) x = -x, putchar('-');
int p = 0;
static char s[100];
do {
s[++p] = x orz 10 + '0';
x /= 10;
} while (x);
while(p) putchar(s[p--]);
putchar(' ');
}
template <typename T, typename... Arp>
inline void write(T &x, Arp &...arp) {
write(x), write(arp...);
return ;
}
template <typename S, typename T>
inline void smax(S &x, T y) {
x = (x > y) ? x : y;
}
template <typename S, typename T>
inline void smin(S &x, T y) {
x = (x < y) ? x : y;
}
inline void quit() {
exit(0);
return ;
}
inline ll quick_pow(ll a, ll b, ll P) {
ll ans = 1;
while(b >= 1) {
if(b & 1) {
ans = ans * a % P;
}
a = a * a % P;
b >>= 1;
}
return ans;
}
template <typename T>
inline T exgcd(T a, T b, T &x, T &y) {
if(b == 0) {
x = 1; y = 0;
return a;
}
int gcd = exgcd(b, a % b, x, y);
int tmp = y;
y = x - a / b * y;
x = tmp;
return gcd;
}
} using namespace Fast_Skm;
const int N = 1e6 + 7;
ll n, a[N], t[N], b[N];
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
read(n);
for (int i = 1; i <= n; ++i)
read(a[i]), b[i] = a[i];
std::sort(b + 1, b + 1 + n); //离散化
for (int i = 1; i <= n; ++i) {
int p = std::lower_bound(b + 1, b + 1 + n, a[i]) - b;
a[i] = p;
}
/*for (int i = 1; i <= n; ++i) {
write(a[i]);
}*/
int l = 1, r = 1, ans = 0;
t[a[1]]++;
while (r < n) { //双指针
while (t[a[r + 1]] == 0 && r < n) {
t[a[r + 1]]++;
r++;
}
smax(ans, r - l + 1);
while (t[a[r + 1]] != 0 && l <= r + 1) {
t[a[l]]--;
l++;
}
}
write(ans);
return 0;
}
本题总结:
本题在 \(S\) 组模拟赛中偏简单,可以一眼看出,但是要保证代码中不出现失误,最好要进行对拍。
\(T3\) magic
题意简述:
有一段数列,和一段排列,问最少从数列中取多少个数和排列中的出相乘,得到的和,能大于等于 \(q\)
思路:
可以贪心的思考,每次去数列和排列中最大的两个数相乘。
观察到排列是一个公差为 \(1\) 的等差数列,我们可以考虑维护一个这样的数组
观察不难发现,此数组正是对后缀和求后缀和的结果。
有了这个数组,我们对于每个 \(p \le N\) 可以用 \(doublesum\) 数组快速地求出来。
但是,如果 \(p > N\) 呢
我们可以再仔细的观察 \(doublesum\) 数组,可以发现当 \(p > N\) 时,我们的数组是这样的 \(\downarrow\)
使用乘法分配律把 \(p - N\) 拆出来,得:
我们发现对于每一个 \(a_i\),它乘上的数都是固定的,不会发生变化,即 \((p - N)\) ,所以我们可以维护一个后缀和数组,每次当 \((p - N)\) 我们就可以用 \(sum\) 和 \(doublesum\) 来 \(O(1)\) 地求出它们的和了。
但是,这有这么用呢,我们要求的不是最少的个数吗?
此问题我们可以转化为,让值最大,取得个数最少,很明显是一道二分板子题,我们可以二分次数,用两个数组快速求出它能带来的贡献,判断合不合法即可,复杂度 \(O(n+mlogn)\)
\(code\)
#pragma G++ optimize(3, "Ofast", "inline")
#pragma G++ optimize(2)
#include <iostream>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <climits>
#include <random>
#include <bitset>
#include <unordered_map>
#define C(y,x) fac[y]/fac[(y)-(x)]/fac[x]
#define _max(a,b) (a>b?a:b)
#define _min(a,b) (a<b?a:b)
#define orz %
#define ll long long
#define int long long
#define juruo Optimist_Skm
#define mid (l + r >> 1)
#define pii std::pair<int, int>
#define fi first
#define se second
#define eb emplace_back
#define pb push_back
#define m_p std::make_pair
#define pq std::priority_queue<int>
//#define pq_min_std::priority_queue<int, std::vector<int>, std::greater<int> >
#define pq_min_pii std::priority_queue<pii, std::vector<pii>, std::greater<pii> >
#define open(x) freopen(#x".in", "r", stdin);freopen(#x".out", "w", stdout);
#define test(x) cout << "Test: " << x << '\n'
#define close fclose(stdin);fclose(stdout);
#define ull unsigned long long
#define debug(); printf("Skmqwq\n");
namespace Fast_Skm {
template <typename T>
inline void read(T &x) {
T s = 0, w = 1;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') w = -1;
c = getchar();
}
while(isdigit(c))
s = (s << 1) + (s << 3) + (c & 0xcf), c = getchar();
x = s * w;
return ;
}
template <typename T, typename... Arp>
inline void read(T &x, Arp &...arp) {
read(x), read(arp...);
return ;
}
template <typename T>
inline void write(T x) {
if(x < 0) x = -x, putchar('-');
int p = 0;
static char s[100];
do {
s[++p] = x orz 10 + '0';
x /= 10;
} while (x);
while(p) putchar(s[p--]);
putchar('\n');
}
template <typename T, typename... Arp>
inline void write(T &x, Arp &...arp) {
write(x), write(arp...);
return ;
}
template <typename S, typename T>
inline void smax(S &x, T y) {
x = (x > y) ? x : y;
}
template <typename S, typename T>
inline void smin(S &x, T y) {
x = (x < y) ? x : y;
}
inline void quit() {
exit(0);
return ;
}
inline ll quick_pow(ll a, ll b, ll P) {
ll ans = 1;
while(b >= 1) {
if(b & 1) {
ans = ans * a % P;
}
a = a * a % P;
b >>= 1;
}
return ans;
}
template <typename T>
inline T exgcd(T a, T b, T &x, T &y) {
if(b == 0) {
x = 1; y = 0;
return a;
}
int gcd = exgcd(b, a % b, x, y);
int tmp = y;
y = x - a / b * y;
x = tmp;
return gcd;
}
} using namespace Fast_Skm;
const int N = 5e5 + 7;
ll n, m;
ll a[N], difsum[N], sum[N];
inline ll min(ll a, ll b) {
return a < b ? a : b;
}
inline ll BinarySearch(ll p, ll q) {
int l = 1, r = min(p, n), best = -1;
while (l <= r) {
if (difsum[mid] + 1ll * (p - mid) * sum[mid] >= q) {
best = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
return best;
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
read(n, m);
for (int i = 1; i <= n; ++i) {
read(a[i]);
}
std::sort(a + 1, a + 1 + n);
memset(difsum, 0, sizeof(difsum));
memset(sum, 0, sizeof(sum));
for (int i = n; i >= 1; --i) {
sum[i] = sum[i + 1] + a[i];
difsum[i] = difsum[i + 1] + sum[i];
}
std::sort(difsum + 1, difsum + 1 + n);
std::sort(sum + 1, sum + 1 + n);
//write(difsum[n], sum[n]);
/*for (int i = 1; i <= n; ++i) {
write(difsum[i], sum[i]);
puts("\n");
}*/
for (int i = 1; i <= m; ++i) {
ll p, q;
read(p, q);
write(BinarySearch(p, q));
}
return 0;
}
本题总结:
这是一道思维题,有一定的思维难度,但只要耐心的去找规律,推式子,还是有机会做出来的(就像这次
\(T4\) 统计区间
题意简述:
很清真的题面,我就直接复制了:
思路:
考试的时候没想出来,打的暴力也爆零了
寄!
考完之后找神仙室友看了一下,他竟然想出了比正解复杂度更低的算法。(前因:我们寝床被小孩哥摇塌了,被迫搬到另一个寝,但没想到那个寝里全是省选班的大佬 比Syx强多了)
正解思路:
可以维护在遍历的过程中维护每个数最新出现的位置,以及次新出现的位置,因为只能出现两次,所以当第三个数出现,次新位置的那个数就必须被弹出,这就可以转化为一道线段树扫描线的板子题了,复杂度 \(O(nlogn)\)。
更优思路:
一段区间,就是一段前缀的后缀,我们可以给每个数一个随机的 \(hash\) 值,在遍历的过程中,维护每个前缀的异或和。
为什么会想到异或和呢,因为异或有一个性质,即异或一次将会是那个数的值,异或两次就刚好消掉,我们可以通过此性质,对于每一段前缀的异或值,用一个 \(unordered_map\) 进行储存,对于当前的前缀,我们求出它的异或值后,只要让答案加上与它异或值相同的前缀的个数即可。
证明
如果两个前缀的异或值完全相同,那么他们的异或和肯定等于 \(0\) ,而这个 \(0\) 就代表这两段前缀和中间的区间的异或值为 \(0\) (因为这两段前缀异或和异或起来,会把小的那段前缀给消掉,自然就剩下中间的那段区间 (和普通前缀和一样)),而这段区间的异或值等于 \(0\) ,就表示它里面所有的元素都出现了偶数次。
但是,题目要求的是两次啊
我们可以借鉴抄袭 正解的做发,维护每个数最新出现的位置,以及次新出现的位置,在遍历的过程中,不断把不合法区间带来的贡献减掉(在 \(unordered \_map\) 操作),这样,就可以保证我们这段区间中所有的数都有且仅有两个,复杂度 \(O(n)\)。
\(code\)
#pragma G++ optimize(3, "Ofast", "inline")
#pragma G++ optimize(2)
#include <iostream>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <climits>
#include <random>
#include <bitset>
#include <unordered_map>
#include <time.h>
#define C(y,x) fac[y]/fac[(y)-(x)]/fac[x]
#define _max(a,b) (a>b?a:b)
#define _min(a,b) (a<b?a:b)
#define orz %
#define ll long long
#define int long long
#define juruo Optimist_Skm
#define mid (l + r >> 1)
#define pii std::pair<int, int>
#define fi first
#define se second
#define eb emplace_back
#define pb push_back
#define m_p std::make_pair
#define pq std::priority_queue<int>
#define pq_min_std::priority_queue<int, std::vector<int>, std::greater<int> >
#define pq_min_pii std::priority_queue<pii, std::vector<pii>, std::greater<pii> >
#define open(x) freopen(#x".in", "r", stdin);freopen(#x".out", "w", stdout);
#define test(x) cout << "Test: " << x << '\n'
#define close fclose(stdin);fclose(stdout);
#define ull unsigned long long
#define debug(); printf("Skmqwq\n");
namespace Fast_Skm {
template <typename T>
inline void read(T &x) {
T s = 0, w = 1;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') w = -1;
c = getchar();
}
while(isdigit(c))
s = (s << 1) + (s << 3) + (c & 0xcf), c = getchar();
x = s * w;
return ;
}
template <typename T, typename... Arp>
inline void read(T &x, Arp &...arp) {
read(x), read(arp...);
return ;
}
template <typename T>
inline void write(T x) {
if(x < 0) x = -x, putchar('-');
int p = 0;
static char s[100];
do {
s[++p] = x orz 10 + '0';
x /= 10;
} while (x);
while(p) putchar(s[p--]);
putchar(' ');
}
template <typename T, typename... Arp>
inline void write(T &x, Arp &...arp) {
write(x), write(arp...);
return ;
}
template <typename S, typename T>
inline void smax(S &x, T y) {
x = (x > y) ? x : y;
}
template <typename S, typename T>
inline void smin(S &x, T y) {
x = (x < y) ? x : y;
}
inline void quit() {
exit(0);
return ;
}
inline ll quick_pow(ll a, ll b, ll P) {
ll ans = 1;
while(b >= 1) {
if(b & 1) {
ans = ans * a % P;
}
a = a * a % P;
b >>= 1;
}
return ans;
}
template <typename T>
inline T exgcd(T a, T b, T &x, T &y) {
if(b == 0) {
x = 1; y = 0;
return a;
}
int gcd = exgcd(b, a % b, x, y);
int tmp = y;
y = x - a / b * y;
x = tmp;
return gcd;
}
} using namespace Fast_Skm;
const int N = 1e6 + 7;
int n, a[N], hash1[N], last[N], se_last[N], hash2[N];
std::mt19937_64 Skmqwq(time(0) ^ clock());
std::unordered_map<int, int> mp;
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
read(n);
for (int i = 1; i <= n; ++i) {
read(a[i]);
}
for (int i = 1; i <= n; ++i) {
hash1[i] = Skmqwq();
}
mp[0] = 1;
int pos = 0, ans = 0;
for (int i = 1; i <= n; ++i) {
while (pos < se_last[a[i]]) mp[hash2[pos]]--, ++pos;
se_last[a[i]] = last[a[i]], last[a[i]] = i;
hash2[i] = hash2[i - 1] ^ hash1[a[i]];
ans += mp[hash2[i]];
mp[hash2[i]]++;
}
write(ans);
return 0;
}
本题总结:
这是一道线段树的板子题,但是我对线段树的了解还不深,也想不出更优的解法,太废了,对于这种题目,要保证暴力的正确性,可行性,同时多做卡常等优化,争取能骗取得更多的分数。
考试总结:
本次考试偏简单,但是也要稳重的对待,稳扎稳打,也要加深对各个算法变式的了解,增强思维强度。

浙公网安备 33010602011771号