2023.7.17 NOIP模拟赛
每次我感觉我能混个100时 我就还是没混上100
就剩两次机会了啊啊啊
赛时记录
开题
A 不会 没思路 暴力很好想 可拍
B 可能和数位有关 不清楚再说
C 也有点思路 但是切C显然不现实
D what?
8:45 A题想到一个 \(60pts\) 的做法 先写吧后面有时间再冲正解
8:57 小样例过了 溜了溜了
9:18 B想到一个可能可行但是大概率会挂掉的代码 但是先码吧
9:40 码完了 大样例寄了
10:00 发现题审错了 **
10:21 发现A题可以离线 那应该就能过了
10:58 写完了样例过了 但是还需要构造一些极限数据
11:08 发现C题爆搜似乎可以拿 \(15pts\) 着手写
11:23 写完了 小样例过了 交吧
最后三十分钟 T2写不出来了 决定拍一下T1保一下
结果我忘了fc指令怎么写了 于是决定手拍
预计:100 + 5 + 15 + 0
实际:60 + 5 + 0 + 0 rk16
赛后总结
\(unsigned\) \(long\) \(long\) 正值域是 \(long\) \(long\) 的 \(2\) 倍 不是翻倍!!!!
开 \(int128\) 就过了 测测测
以后 \(long\) \(long\) 相乘记得开 \(int128\)
补题
A.
据说这玩意是 IMO 的题
然而放到这实际上就是打表找规律 所以很容易写出 \(60pts\) 的代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6 + 0721;
int d[N], ans[N];
vector<int> v[101];
int T, n;
void init() {
d[1] = 1;
for (int i = 2; i <= 100; ++i) {
v[i].push_back(i);
v[i].push_back(i * i * i);
++d[i * i * i];
}
for (int i = 2; i <= 100; ++i) {
int k = i * i;
for (int j = 2; 1; ++j) {
ll tmp;
tmp = 1ll * k * v[i][j - 1] - v[i][j - 2];
if (tmp > 1000000) break;
++d[tmp];
v[i].push_back((int)tmp);
}
}
for (int i = 1; i <= 1000000; ++i) {
ans[i] = ans[i - 1] + d[i];
}
}
int main() {
scanf("%d", &T);
init();
while (T--) {
scanf("%d", &n);
printf("%d\n", ans[n]);
}
return 0;
}
然后我们发现 这个做法显然复杂度是 \(O(ans)\) (答案个数)的
那么瓶颈其实在于开不下 \(10^{18}\) 的前缀和数组
然后我们发现询问可以离线 这样我们可以建以询问编号为下标的前缀和数组 然后二分出当前这个数应该插入的位置
因为赛时我在写B题 加上可能哪根弦没搭上所以用的 \(BIT\) 实际上直接求反而会更快
code:
#include <bits/stdc++.h>
#define ll __int128
using namespace std;
const int N = 1e6 + 0721;
const int M = 1e5 + 0721;
ll ans[N];
vector<ll> v[N];
int T;
ll maxn;
struct node {
ll val;
int id;
friend bool operator<(node x, node y) {
return x.val < y.val;
}
} q[M];
struct BIT {
ll tr[M];
inline int lowbit(int x) {
return x & (-x);
}
void update(int x, int val) {
while (x <= T) {
tr[x] += val;
x += lowbit(x);
}
}
ll query(int x) {
ll ret = 0;
while (x) {
ret += tr[x];
x -= lowbit(x);
}
return ret;
}
} sum;
int binary_search(ll val) {
int l = 1, r = T;
int mid, ans = -1;
while (l <= r) {
mid = (l + r >> 1);
if (q[mid].val >= val) {
ans = mid;
r = mid - 1;
} else
l = mid + 1;
}
return ans;
}
void insert(ll x) {
int loc = binary_search(x);
if (loc == -1) return;
sum.update(loc, 1);
}
void dp() {
insert(1);
for (ll i = 2; 1; ++i) {
if (i * i * i > maxn) break;
v[i].push_back(i);
v[i].push_back(i * i * i);
insert(i * i * i);
}
for (ll i = 2; v[i].size(); ++i) {
ll k = i * i;
for (int j = 2; 1; ++j) {
ll tmp;
tmp = k * v[i][j - 1] - v[i][j - 2];
if (tmp > maxn) break;
insert(tmp);
v[i].push_back(tmp);
}
}
}
int main() {
scanf("%d", &T);
for (int i = 1; i <= T; ++i) {
scanf("%lld", &q[i].val);
q[i].id = i;
maxn = max(maxn, q[i].val);
}
sort(q + 1, q + 1 + T);
dp();
for (int i = 1; i <= T; ++i) {
ans[q[i].id] = sum.query(i);
}
for (int i = 1; i <= T; ++i) printf("%lld\n", ans[i]);
return 0;
}
/*
10
10
100
1000
10000
100000
114514
1919810
20190104
123123123123
10000001000000
*/
B.
转化一下题的意思 就会发现实际的意思是 \(a_z\) 是它前缀的最大/最小值
那么我这个子序列一定是长这样的:
图
所以我们以第一个点为分界的话 那么这条线上方就是一个单增子序列 下方是一个单减子序列
所以我们枚举第一个点 并求出以它为头的单增/单减子序列数量 乘起来就是这个点的方案数
code:
//假代码
//这回成真的了
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 0721;
const int mod = 998244353;
ll f[N][2];
int a[N];
int n, T;
ll ans;
struct BIT {
ll tr[N];
inline int lowbit(int x) {
return x & (-x);
}
void update(int x, ll val) {
while (x <= n) {
tr[x] = (tr[x] + val) % mod;
x += lowbit(x);
}
}
ll query(int x) {
ll ret = 0;
while (x) {
ret = (ret + tr[x]) % mod;
x -= lowbit(x);
}
return ret;
}
};
BIT sum[2];
void dp() {
ans = 0;
for (int i = 1; i <= n; ++i) {
f[i][0] = (sum[0].query(a[i]) + 1) % mod;
f[i][1] = (mod + sum[1].query(n) - sum[1].query(a[i]) + 1) % mod;
sum[0].update(a[i], f[i][0]);
sum[1].update(a[i], f[i][1]);
ans = (ans + f[i][0] % mod * f[i][1]) % mod;
// cout << f[i][0] << " " << f[i][1] << "\n";
}
for (int i = 0; i < 2; ++i) {
for (int j = 1; j <= n; ++j) sum[i].tr[j] = 0;
}
}
int main() {
ios::sync_with_stdio(false);
// freopen("ex_sequence2.in", "r", stdin);
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[n - i + 1];
dp();
cout << ans << "\n";
}
return 0;
}
/*
1
4
1 3 2 4
1
10
7 3 4 2 9 8 5 6 1 10
*/
C.
非常非常非常玄学的一道题
区间问题起手排序没啥好说的
然后我们非常玄学地注意到有这么一个特殊性质的部分分:区间没有包含关系
考场上看到这玩意感觉很扯淡 没往那方面想
结果正解偏偏就是和那玩意有关
因为包含小区间的大区间 如果把它送到小区间那个集合里 是什么都不会改变的
如果我们把它单拎出来 对答案的贡献就是它的区间长度
所以我们先把所有包含其它区间的区间拎出来 并算出给它们分 \(i\) 个区间得到的最大价值(然后把剩下的都插入到对应的它包含的小区间所在的集合)
然后我们考虑把小区间分成 \(k - i\) 个区间所得到的最大价值 这时候左端点排序就有用了
因为如果它们之间交集不为空 那么一定是对应排完序后连续的一段
所以我们如果设 \(f_{i, j}\) 表示第 \(i\) 个区间 把它和它前面的区间分成 \(j\) 组的最大价值
显然有 \(f_{i, j} = max(f_{k, j - 1} + a[k + 1].r - a[i].l)\) 并且要求 \(a[k + 1].r > a[i].l\)
又因为我们是按左端点排序的 所以如果 \(a[k + 1].r \le a[i].l\) 时 它一定也满足 \(a[k + 1].r \le a[i + 1].l\)
我们枚举的这段 \(\left[k + 1, r\right]\) 这段区间其实是一个类似于双指针的东西
所以就可以单调队列优化
code:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5210;
bool vis[N];
ll f[N][N], g[N];
int q[N], h, t;
int n, k;
ll ans;
vector<int> v;
struct node {
int l, r;
friend bool operator<(node x, node y) {
return x.l < y.l;
}
};
node a[N], b[N];
inline bool cmp(int x, int y) {
return x > y;
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i) scanf("%d%d", &a[i].l, &a[i].r);
for (int i = 1; i <= n; ++i) { //找包含其它区间的大区间
for (int j = 1; j <= n; ++j) {
if (i == j) continue;
if (a[i].l <= a[j].l && a[i].r >= a[j].r && !vis[i]) {
vis[i] = 1;
v.push_back(a[i].r - a[i].l);
}
}
}
int cnt = 0;
for (int i = 1; i <= n; ++i) {
if (!vis[i]) b[++cnt] = a[i]; //把小的区间都选出来
}
sort(v.begin(), v.end(), cmp);
for (int i = 1; i <= v.size(); ++i) g[i] = g[i - 1] + v[i - 1]; //把g数组算出来
sort(b + 1, b + 1 + cnt);
memset(f, -0x3f, sizeof f); //因为有状态不合法 并且转移式会让非法状态不为0
f[0][0] = 0;
for (int j = 1; j <= k; ++j) {
h = 0, t = -1;
q[++t] = j - 1;
for (int i = j; i <= cnt; ++i) { //由j - 1来 所以至少得把前面j - 1都分了
while (h <= t && b[q[h] + 1].r <= b[i].l) ++h; //前面的转移式看懂这里的单调队列不难理解
if (h <= t) f[i][j] = max(f[i][j], f[q[h]][j - 1] + b[q[h] + 1].r - b[i].l);
while (h <= t && f[i][j - 1] + b[i + 1].r - b[i].l > f[q[t]][j - 1] + b[q[t] + 1].r - b[i].l) --t;
q[++t] = i;
}
}
for (int j = 0; j <= v.size() && k - j > 0; ++j) {
if (f[cnt][k - j] != 0) {
ans = max(ans, g[j] + f[cnt][k - j]);
}
}
printf("%lld", ans);
return 0;
}
/*
4 2
1 3
1 5
4 6
2 7
*/
D.
看不懂 咕