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.
看不懂 咕

posted @ 2023-07-17 16:08  Steven24  阅读(35)  评论(2编辑  收藏  举报