2023.7.7 NOIP模拟赛
赛时记录
A.发现没敢给大样例 应该是诈骗题 等会回来再看
B.首先想到区间
C.没看懂 再说吧 反正
D.怎么又是数论啊 难绷 可能能用
回来看A题吧 前30分的暴力分都很好敲 先不管
我们考虑
但是这是不可能的 因为没有限制的数据有 70 分 要是固输 NO 有 70 分就太扯淡了 这题还没有多测
首先我们要明确一个问题 就是对于每一个骰子而言 它六面的数字应该是全部等价的 因为最上面的数字非常的随机
然后呢我们发现不可能让每个骰子每一位都是相同的 所以不能限制比如固定哪一位是
比如说
那么它们在那几位上就不等价了 那就无解了
然后我们发现
那么我们现在需要找一个这样的数 我们保证后面几位 前面数字不管是啥都是它的倍数
然后我们发现
那么我们盲猜只有
反正这么干那30应该是稳的 噢对需要特判一下
但是疑点就在于数的取值范围
发现好像不对 只要选
upd:上面这玩意确实假了
看B题看能写出来多少 记得留大概1.5h去写T3的疑似状压和T4的
首先这个查区间
首先
首先贪心是不可以的 比如 0 1 2 3 4 5 6 7 0 8 9 是不可能嗯贪的
问题还是部分分给的太少了啊啊啊啊啊啊啊啊
我们考虑每个元素对答案的贡献 当且仅当它是前面一段区间的
问题又来了 实际上不仅仅只是+1 测。
还剩两个小时 切不掉了 写部分分吧 **
01性质挂了 有时间记得回来修
T4的
啥也别说了 修吧。
破防了 怎么这么菜啊。
调不出来 心态炸了。
A题假做法 B题调不出来 C会写但不完全会写 D写炸了
不是为什么我小数和样例输出都能对上一到取模的就歇菜啊
我寻思我模数也妹抄错啊
不是为啥还要加捆绑测试啊
还剩15分钟 无所谓了已经
真考虑考虑要不滚回去学文化课吧
预计:30 + 20 + 0 + 0
实际:30 + 20 + 15 + 0 rk15
C题固输NO混了15分
考完 bot 告诉我
好似。
我学到了什么
开玩笑 啥也没学到
知识层面:
- 有的题确实不会出现输出NO的情况
- 当某些数据范围很小可以考虑把它当常数想做法
不能取模!!!!- 因为对
进行质因数分解时 的质因数最多只会出现一次 所以可以考虑以 为界分开处理
策略层面:
- 首先别老惦记着切题 把能打出来的暴力都打了再说 你要是没切掉暴力还没打出来不就完犊子了吗?你要是前面没切掉你还有心情打暴力?你要是没有暴力你想拍拿什么拍?
- 记得把每道题的暴力都预留好足够的时间 要是一道题实在修不出来就放了吧
补题
A.
首先问题可以简化成选六个不重复的数 让每个骰子上都是这六个数 使得从中选择任意个都是
那我们很明显假如说弄一车的
但是要弄六个不同的 其中肯定有
我们考虑 因为
那我如果在
所以我们能构造出
那么进一步 再塞到
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.
首先就像赛时说的
然后转移的时候枚举那个取
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;
}
那我们考虑优化 首先
并且我们发现
我们可以维护出每个
这里选择建了一棵权值线段树 然后查找前缀所有数出现的最早时间
据说有一种用桶的
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.
实际上就是找若干个简单环 然后判这些环能不能恰好不重不漏地覆盖所有点
那实际上就是判能不能删一些边让每个点的出度入度都为
那我们把每个点的出点和入点分开 然后入点都连源点 出点都连汇点 每条边的最大流量都为
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.
一个很典但是我不知道的东西:对于每个数
所以我们将质因子分为
因为我们实际上只需要查区间每个质因子出现次数的最大值
对于小质数 我们给每个小质数开一棵线段树维护其区间出现次数最大值
对于大质数 我们维护每个大质数的前缀和 然后作差判断这个区间内有没有出现即可 因为只要出现了次数一定是
这样就是
据说要切掉需要 莫队 +
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具