2023.7.7 NOIP模拟赛

赛时记录

A.发现没敢给大样例 应该是诈骗题 等会回来再看
B.首先想到区间\(DP\) 但是复杂度挂了
C.没看懂 再说吧 反正 \(40pts\) 应该是状压
D.怎么又是数论啊 难绷 可能能用 \(ST\) 表暴力骗点分

回来看A题吧 前30分的暴力分都很好敲 先不管
我们考虑 \(d = 3\) 的情况 一个很直觉的想法就是只要 \(d\) 不是 \(2\) 就都无解
但是这是不可能的 因为没有限制的数据有 70 分 要是固输 NO 有 70 分就太扯淡了 这题还没有多测

首先我们要明确一个问题 就是对于每一个骰子而言 它六面的数字应该是全部等价的 因为最上面的数字非常的随机
然后呢我们发现不可能让每个骰子每一位都是相同的 所以不能限制比如固定哪一位是 \(0\)
比如说\(3\) 我们可以构造它的倍数 \(6:110\) 或者是 \(12:1100\) 那我们的想法只能是保证后面几位的 前面因为不同数字间必然至少会有几位会有\(10\)的差异
那么它们在那几位上就不等价了 那就无解了
然后我们发现\(1110\)\(14\)不是\(6\)的倍数 \(10110\)\(22\)也不是\(6\)的倍数
那么我们现在需要找一个这样的数 我们保证后面几位 前面数字不管是啥都是它的倍数
然后我们发现\(4:100\) \(1100\)\(12\)是它的倍数 \(1000\)\(8\)是它的倍数 只要我们能保证最后两位是\(0\) 那么前面若干位就表示是\(4\)的几倍
那么我们盲猜只有\(d\)\(2\)的倍数才有解 否则无解(也只能这么干了)
反正这么干那30应该是稳的 噢对需要特判一下\(n=1\)
但是疑点就在于数的取值范围 \(n\)的范围与 \(d\) 的范围 但我也没啥别的招了()
发现好像不对 只要选\(6\)个数出来 它们里面随便选任何个异或和都为\(d\)的倍数即可 \(6\)的枚举也就\(63\)种 可以爆搜 但是先不考虑写 比较耽误时间

upd:上面这玩意确实假了

看B题看能写出来多少 记得留大概1.5h去写T3的疑似状压和T4的\(ST\)
首先这个查区间 \(mex\) 大不了上主席树 先不管 主要考虑怎么分
首先 \(n^2 logn\) 的暴力 \(DP\) 很好想 但是有着与其分值配不上的码量
\(c_i\) 怎么只有20啊/fn 要我我就出到 \(10^5\) 强制码主席树((
首先贪心是不可以的 比如 0 1 2 3 4 5 6 7 0 8 9 是不可能嗯贪的
问题还是部分分给的太少了啊啊啊啊啊啊啊啊

我们考虑每个元素对答案的贡献 当且仅当它是前面一段区间的 \(mex\) 时 选它才会让答案+1 否则对答案没有贡献
问题又来了 实际上不仅仅只是+1 测。
还剩两个小时 切不掉了 写部分分吧 **
01性质挂了 有时间记得回来修

T4的\(ST\)表写完了 写挂了。
啥也别说了 修吧。
破防了 怎么这么菜啊。

调不出来 心态炸了。
A题假做法 B题调不出来 C会写但不完全会写 D写炸了
不是为什么我小数和样例输出都能对上一到取模的就歇菜啊
我寻思我模数也妹抄错啊
不是为啥还要加捆绑测试啊
还剩15分钟 无所谓了已经
真考虑考虑要不滚回去学文化课吧

预计:30 + 20 + 0 + 0
实际:30 + 20 + 15 + 0 rk15

C题固输NO混了15分
考完 bot 告诉我 \(lcm\) 不能取模 所以啥也没写挂但是调不出来
好似。


我学到了什么

开玩笑 啥也没学到

知识层面:

  • 有的题确实不会出现输出NO的情况
  • 当某些数据范围很小可以考虑把它当常数想做法
  • \(lcm\) 不能取模!!!!
  • 因为对 \(x\) 进行质因数分解时 \(> \sqrt{x}\) 的质因数最多只会出现一次 所以可以考虑以 \(\sqrt x\) 为界分开处理

策略层面:

  • 首先别老惦记着切题 把能打出来的暴力都打了再说 你要是没切掉暴力还没打出来不就完犊子了吗?你要是前面没切掉你还有心情打暴力?你要是没有暴力你想拍拿什么拍?
  • 记得把每道题的暴力都预留好足够的时间 要是一道题实在修不出来就放了吧

补题

A.

首先问题可以简化成选六个不重复的数 让每个骰子上都是这六个数 使得从中选择任意个都是 \(d\) 的倍数
那我们很明显假如说弄一车的 \(0\) 就可以让它们异或起来毫无影响
但是要弄六个不同的 其中肯定有 \(0\)\(d\)
我们考虑 因为 \(d\le 60\) 所以它的二进制最多有 \(6\)
那我如果在 \(7-12\) 位塞和 \(d\)\(1-6\) 位一样的 那最后答案有没没有这个一定都是 \(d\) 的倍数 (因为有的话就相当于 \(d\)\(64\) 位)
所以我们能构造出 \(d000000\)\(dd\)
那么进一步 再塞到 \(13-18\) 位 然后就做完了
code:

#include <bits/stdc++.h>
using namespace std;
int n, d;
int main() {
	scanf("%d%d", &n, &d);
	printf("Yes\n");
	for (int i = 1; i <= n; ++i) printf("%d %d %d %d %d %d\n", 0, d, 64 * d, 64 * d + d, 64 * 64 * d, 64 * 64 * d + d);
	return 0;
}

B.

首先就像赛时说的 \(O(n ^ 2)\)\(DP\) 很白给 直接设 \(f_i\) 表示 \(i\) 位置的最大美丽度
然后转移的时候枚举那个取 \(mex\) 的区间即可 可以暴力统计
code:

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

const int N = 5e5 + 0721;
int a[N], f[N], pre[N], ans[N], top, sum;
bool vis[31];
int n;

int find_mex(int l, int r) {
	for (int i = 0; i <= 25; ++i) vis[i] = 0;
	for (int i = l; i <= r; ++i) vis[a[i]] = 1;
	for (int i = 0; i <= 25; ++i) {
		if (!vis[i]) return i;
	}
}

void solve1() {
	for (int i = 1; i <= n; ++i) {
		for (int j = 0; j < i; ++j) {
			int mex = find_mex(j + 1, i);
			if (f[i] < f[j] + mex) {
				pre[i] = j + 1;
				f[i] = f[j] + mex;
//				cout << i << " " << pre[i] << endl;
			}
		}
	}
	int tmp = n;
	while (tmp != 0) {
//		cout << tmp << " ";
		ans[++top] = pre[tmp];
		tmp = pre[tmp] - 1;
	}
	
	printf("%d %d\n", f[n], top);
	for (int i = top; i >= 1; --i) printf("%d ", ans[i]);
}

void solve2() {
	for (int i = 1; i <= n; ++i) {
		if (a[i] == 0) {
			if (vis[1]) sum += 2;
			else {
				++sum;
				ans[++top] = i;
				vis[1] = 0;
				vis[0] = 1;
			}
		} else {
			if (vis[1] == 0) {
				vis[1] = 1;
				if (vis[0]) ++sum;
			}
			else {
				ans[++top] = i;
				vis[1] = 1;
				vis[0] = 0;
			}
		}
	}
	
	printf("%d %d\n", sum, top);
	for (int i = 1; i <= top; ++i) printf("%d ",ans[i]);
}

int main() {
//	freopen("cut3.in", "r", stdin);
	
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	
	if (n > 1e3) solve2();
	else solve1();
	
	return 0;
}

那我们考虑优化 首先 \(f_i\) 一定是单调不减的一段序列
并且我们发现 \(c_i \le 20\) 这说明 \(mex\) 一共就只有 \(22\) 种取值
我们可以维护出每个 \(i\) 能取到每种 \(mex\) 的最靠后的位置 然后枚举 \(22\) 种取值即可
这里选择建了一棵权值线段树 然后查找前缀所有数出现的最早时间
据说有一种用桶的 \(O(n)\) 的预处理
code:

#include <bits/stdc++.h>
#define ls (k << 1)
#define rs (k << 1 | 1)
#define mid (l + r >> 1)
using namespace std;

const int N = 5e5 + 0721;
int mex[N][25], f[N], pre[N], a[N];
int ans[N], top;
int n;

struct tree {
	int minn[101];
	
	void modify(int k, int l, int r, int loc, int v) {
		if (l == r) {
			minn[k] = v;
			return;
		}
		if (loc <= mid) modify(ls, l, mid, loc, v);
		else modify(rs, mid + 1, r, loc, v);
		minn[k] = min(minn[ls], minn[rs]);
	}
	
	int query(int k, int l, int r, int u, int v) {
		if (u <= l && v >= r) return minn[k];
		int ret = 0x7fffffff;
		if (u <= mid) ret = min(ret, query(ls, l, mid, u, v));
		if (v > mid) ret = min(ret, query(rs, mid + 1, r, u, v));
		return ret;
	}
} seg;

void init() {
	for (int i = 1; i <= n; ++i) {
		seg.modify(1, 0, 21, a[i], i);
		for (int j = 1; j <= 21; ++j) {
			mex[i][j] = seg.query(1, 0, 21, 0, j - 1);
//			cout << i << " " << j << " " << mex[i][j] << endl;
		}
	}
}

void dp() {
	for (int i = 1; i <= n; ++i) pre[i] = 1;
	
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= 21; ++j) {
			if (mex[i][j] == 0) continue;
//			cout << i << " " << j << " " << mex[i][j] << endl;
			if (f[i] < f[mex[i][j] - 1] + j) {
				pre[i] = mex[i][j];
				f[i] = f[mex[i][j] - 1] + j;
//				cout << i << " " << f[i] << " " << pre[i] << endl;
			}
		}
		if (f[i - 1] > f[i]) {
			pre[i] = pre[i - 1];
			f[i] = f[i - 1];
		}
	}
}

void get_ans() {
	int tmp = n;
//	cout << pre[tmp] << endl;
	while (tmp > 0) {
		ans[++top] = pre[tmp];
		tmp = pre[tmp] - 1;
//		cout << tmp << endl;
		if (tmp < 0) break;
	}
	
	printf("%d %d\n", f[n], top);
	for (int i = top; i >= 1; --i) printf("%d ", ans[i]);
}

int main() {
//	freopen("cut3.in", "r", stdin);
	
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	init();
	dp();
	get_ans();
	
	return 0;
}

C.

实际上就是找若干个简单环 然后判这些环能不能恰好不重不漏地覆盖所有点
那实际上就是判能不能删一些边让每个点的出度入度都为 \(1\)
那我们把每个点的出点和入点分开 然后入点都连源点 出点都连汇点 每条边的最大流量都为 \(1\) 跑最大流 判这个最大流和 \(n\) 等不等即可
code:

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

const int N = 2e6 + 0721;
const ll inf = 0x7ffffffffffffff;
int dis[N], cur[N];
int head[N], nxt[N], to[N], val[N], cnt = 1;
int n, m, s, t;
ll ans;
vector<int> v[N];
int _nxt[N], tot;
bool vis[N];

inline void cmb(int x, int y, int z) {
	to[++cnt] = y;
	val[cnt] = z;
	nxt[cnt] = head[x];
	head[x] = cnt;
} 

bool bfs() {
	queue<int> q;
	for (int i = s; i <= t; ++i) dis[i] = -1;
	for (int i = s; i <= t; ++i) cur[i] = head[i];
	dis[s] = 0;
	q.push(s);
	while (!q.empty()) {
		int now = q.front();
		q.pop();
		if (now == t) return 1;
		for (int i = head[now]; i; i = nxt[i]) {
			int y = to[i];
			if (dis[y] == -1 && val[i] > 0) { 
				dis[y] = dis[now] + 1;
				q.push(y);
			}
		}
	}
	return 0;
}

int dinic(int x, ll res) { 
	if (x == t || res == 0) return res; 
	ll flow = 0, c; 
	for (int i = cur[x]; i != 0 && res; i = nxt[i]) {  
		cur[x] = i;
		int y = to[i];
		if (val[i] > 0 && dis[y] == dis[x] + 1) { 
			c = dinic(y, min(res, (ll)val[i])); 
			if (c == 0) dis[y] = -1;  
			val[i] -= c; 
			val[i ^ 1] += c; 
			flow += c;
			res -= c;
		}
	}
	return flow;
}

int main() {
//	freopen("hamilton1.in", "r", stdin); 
	
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int x, y;
		scanf("%d%d", &x, &y);
		cmb(x, y + n, 1);
		cmb(y + n, x, 0);  
	}
	
	s = 0, t = (n << 1) + 1;
	
	for (int i = 1; i <= n; ++i) {
		cmb(s, i, 1);
		cmb(i, s, 0);
		cmb(i + n, t, 1);
		cmb(t, i + n, 0);
	}
	
	while (bfs()) ans += dinic(s, inf);
	
//	cout << ans << endl;
	
	if (ans == n) printf("YES\n");
	else {
		printf("NO\n");
		return 0;
	}
	
	for (int i = 1; i <= n; ++i) {
		for (int j = head[i]; j; j = nxt[j]) {
			if (val[j] == 0) {
				_nxt[i] = to[j];
				break;
			} 
		}
	}
	
	for (int i = 1; i <= n; ++i) {
		if (!vis[i]) {
			++tot;
			int tmp = i;
			while (!vis[tmp]) {
				vis[tmp] = 1;
				v[tot].push_back(tmp);
				tmp = _nxt[tmp] - n;
//				cout << tmp << endl;
			}
		}
	}
	
	for (int i = 1; i <= tot; ++i) {
		printf("%d ",v[i].size());
		for (int j = 0; j < v[i].size(); ++j) printf("%d ",v[i][j]);
	}
	
	return 0;
}

D.

一个很典但是我不知道的东西:对于每个数 \(x\) 而言 \(>\sqrt{x}\) 的质因子只会出现一次
所以我们将质因子分为 \(\le \sqrt{x}\)\(>\sqrt{x}\) 的两类
因为我们实际上只需要查区间每个质因子出现次数的最大值
对于小质数 我们给每个小质数开一棵线段树维护其区间出现次数最大值
对于大质数 我们维护每个大质数的前缀和 然后作差判断这个区间内有没有出现即可 因为只要出现了次数一定是 \(1\)
这样就是 \(70pts\)
据说要切掉需要 莫队 + \(bitset\) + 分块一堆东西来优化求大质数那个

posted @ 2023-07-07 13:41  Steven24  阅读(44)  评论(1编辑  收藏  举报