DAY 1
T1
居然花了 30 min 才过掉… 考场上一直担心 unsigned long long
溢出的问题,也就是 1ull << 64
会爆掉,最后居然用 -1ull
来表示$2^{64}-1$。没有任何时间在 Linux 下跑一遍,所以回家之后立刻打开 NOI Linux 虚拟机开始默写 T1 代码,确认 AC 之后才放心了一些。
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
int n;
ull k;
int res[100];
void generate(int cur, ull k) {
if (cur == 1) {
if (k)
res[1] = 1;
else
res[1] = 0;
return ;
}
ull les = 1ull << (cur - 1);
ull tot = 1ull << cur;
if (k >= les) {
res[cur] = 1;
generate(cur - 1, cur != 64 ? tot - k - 1 : -1ull - k);
} else {
res[cur] = 0;
generate(cur - 1, k);
}
return ;
}
int main() {
freopen("code.in", "r", stdin);
freopen("code.out", "w", stdout);
cin >> n >> k;
generate(n, k);
for (int i = n; i; i--)
printf("%d", res[i]);
fclose(stdin);
fclose(stdout);
return 0;
}
T2
一看题以为是非常简单的树形 dp,读题过程中以为是前缀和 + 倍增,仔细一想居然只想出来$O(n^2)$的暴力 TwT 沉思半个小时之后终于反应过来用$f[u]$表示从$1$到$u$链上的合法括号子序列个数,而$f_2[u]$表示强制选取$u$时的合法括号子序列个数。可以发现无论何种情况下$u$都必须继承其父亲的$f[]$。转移的时候,若$u$上是左括号,则直接压入栈中;若$u$上是右括号,则考虑从栈中取一个左括号来和当前右括号匹配,并且观察取出的左括号节点的父亲是否为右括号节点,若是,则需要继承其$f_2[]$;否则只考虑当前的$f_2[]$,最后用$f_2[u]$更新$f[u]$。
考场上就发现$n=114514$的$\Large\text{恶臭数据}$能够把我卡爆,一开始我怀疑是自己的手写栈爆掉了,就维护了一个 ins[]
表示节点是否在栈内,然后在回溯时非常谨慎地处理;后来又怀疑是建边建出了环,但是我一想,这里建的单向边怎么可能出问题呢?于是又开始怀疑是我遍历邻接表的时候写挂了。仔细检查了半个小时之后,我发现只要 dfs
过程中少定义几个变量就可以多跑一会儿才 RE,脑子一热:系统栈爆了!立即开始改为非递归程序。然而这东西我从来没有自己实现过(也从来没想到会考到这东西),面对复杂的回溯我一筹莫展,改的时候又忘记了执行可持久化代码,最后 Ctrl + Z 到底都没能再跑对小样例,转头又开始重构…
此时只剩下一个小时,我的 T3 还未开动,于是我放弃了 T2,希望能在 T3 中链 / 菊花图的特殊情况骗些分(参考去年 D1T3)。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 10;
typedef long long ll;
struct sidetable {
int to, next;
} edge[MAXN << 1];
int head[MAXN], id = 0;
char a[MAXN];
int n;
ll f[MAXN], f2[MAXN];
int s[MAXN], top = 0;
int fa[MAXN], dep[MAXN], v;
bool ins[MAXN];
ll ans = 0;
// f2: 强制以自己结尾
inline void build_edge(int u, int v) {
edge[++id].to = v;
edge[id].next = head[u];
head[u] = id;
return ;
}
void dfs(int u, int father) {
/*
printf("#%d tmp stack: ", u);
for (int i = 1; i <= top; i++)
printf("%d ", s[i]);
putchar('\n');
*/
fa[u] = father;
dep[u] = dep[fa[u]] + 1;
f[u] = f[fa[u]];
f2[u] = 0;
if (a[u] == '(') {
// (
s[++top] = u;
ins[u] = true;
for (int i = head[u]; i; i = edge[i].next) {
v = edge[i].to;
dfs(v, u);
}
while (top && dep[s[top]] >= dep[u])
ins[s[top--]] = false;
} else {
// )
if (top > 0) {
// 还能再次匹配括号
f2[u] = 1;
// 强制选自己只有一种情况(单匹配)
if (a[fa[s[top]]] == ')')
// 凑出 (A)(B)
f2[u] += f2[fa[s[top]]];
f[u] += f2[u];
int pre = s[top--];
ins[pre] = false;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
dfs(v, u);
}
if (!ins[pre]) {
s[++top] = pre;
ins[pre] = true;
}
} else for (int i = head[u]; i; i = edge[i].next) {
v = edge[i].to;
dfs(v, u);
}
}
return ;
}
int main() {
freopen("brackets.in", "r", stdin);
freopen("brackets.out", "w", stdout);
memset(f, 0, sizeof(f));
memset(f2, 0, sizeof(f2));
memset(head, 0, sizeof(head));
memset(ins, 0, sizeof(ins));
scanf("%d", &n);
scanf("%s", a + 1);
for (int i = 2, from; i <= n; i++) {
scanf("%d", &from);
build_edge(from, i);
}
dfs(1, 0);
for (int i = 1; i <= n; i++)
ans = ans ^ (1ll * i * f[i]);
printf("%lld", ans);
fclose(stdin);
fclose(stdout);
return 0;
}
/*
15
()((()))())))()
1 2 3 4 5 6 7 8 9 5 11 12 13 14
6
(()())
1 2 3 4 5
*/
T3
然后我发现我想错了。
T3 死磕半个小时没有任何思路,一开始依靠$n\leq2000$盲猜二分图,但是二分图在这道题没有任何意义;最后看出来像是个贪心,不过我无法在短时间内实现这个复杂的贪心。于是用邻接矩阵开始写暴搜。结果只剩二十分钟的时候,暴搜也跑挂了,我非常紧张,万不得已,搬上 next_permutation()
生成断边序列,然后每次模拟断边,最后维护字典序最小的答案。这样的复杂度是$O(T\cdot n\cdot n!)$,显然会挂,然而等我写完的时候已经只剩五分钟了,所以对于其他情况我非常气愤地敲上了直接无脑输出$1\sim n$的骗分程序,并最后检查了一遍文件操作。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2010;
struct sidetable {
int u, v;
} edge[MAXN];
int n, a[MAXN], id[MAXN];
int ans[MAXN], tmp[MAXN];
bool flag;
namespace FORCE {
int seq[11];
int tmp_a[MAXN], tmp_id[MAXN];
void generate() {
for (int i = 1; i <= 10; i++)
seq[i] = i;
do {
for (int i = 1; i <= n; i++)
tmp_a[i] = a[i], tmp_id[i] = id[i];
for (int i = 1; i < n; i++) {
int u = edge[seq[i]].u, v = edge[seq[i]].v;
tmp_id[tmp_a[u]] = v, tmp_id[tmp_a[v]] = u;
swap(tmp_a[u], tmp_a[v]);
}
flag = false;
for (int i = 1; i <= n && !flag; i++)
if (tmp_id[i] < ans[i]) {
flag = true;
break;
} else if (tmp_id[i] > ans[i])
break;
if (flag) {
for (int i = 1; i <= n; i++)
ans[i] = tmp_id[i];
}
} while (next_permutation(seq + 1, seq + n));
}
void solve() {
memset(ans, 0x3f, sizeof(ans));
// dfs(n);
generate();
for (int i = 1; i <= n; i++)
printf("%d ", ans[i]);
putchar('\n');
}
};
int main() {
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
int T;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1, num; i <= n; i++) {
scanf("%d", &num);
a[num] = i;
id[i] = num;
}
for (int i = 1, u, v; i < n; i++) {
scanf("%d %d", &u, &v);
edge[i].u = u, edge[i].v = v;
}
if (n <= 100) {
FORCE::solve();
} else {
for (int i = 1; i <= n; i++)
printf("%d ", i);
putchar('\n');
}
}
fclose(stdin);
fclose(stdout);
return 0;
}
总结
预估分数:$155\sim 190$,T2 的真实分数完全看 Core i7-8700K 的系统栈究竟有多大,以及数据中的树究竟好不好看(如果长得怪迷日眼的我就真没了)。
由于今天翻车,如果还想拿七级认证(省一),明天的压力很大。今天考到的题目并没有拉开选手差距,我比较希望明天考思维难度大的题目。
DAY 2
T1
开幕雷击!D2T1 直接将我拿到$400$分的期望彻底粉碎。一开始我画出了浅显易懂的分析示意图,以为是个很简单的组合数 + 前缀和优化,然而仔细一想似乎并没有这么简单。毕竟$a[i][j]$是变动的,组合数并不能算出前缀非法状态个数。抱着头沉思一个小时,最后万不得已打出了 dfs 骗分,然后匆匆想 T2 去了。
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
typedef long long ll;
const int MAXN = 110;
const int MAXM = 2010;
int n, m, a[MAXN][MAXM];
int cnt[MAXM];
int dfs(int lim, int now, int rem) {
if (!rem)
return 1;
int res = 0;
for (int i = 1; i <= m; i++)
if (((cnt[i] + 1) << 1) <= lim) {
if (rem == 1)
res += a[now][i];
else {
cnt[i]++;
for (int j = now + 1; n - j + 1 >= rem - 1; j++)
res = (0ll + res + 1ll * a[now][i] * dfs(lim, j, rem - 1) % mod) % mod;
cnt[i]--;
}
}
return res % mod;
}
int main() {
freopen("meal.in", "r", stdin);
freopen("meal.out", "w", stdout);
memset(cnt, 0, sizeof(cnt));
scanf("%d %d", &n, &m);
for (register int i = 1; i <= n; i++)
for (register int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
int ans = 0;
for (register int tot = 2; tot <= n; tot++)
for (register int j = 1; n - j + 1 >= tot; j++)
ans = (0ll + ans + dfs(tot, j, tot)) % mod;
printf("%d", ans % mod);
fclose(stdin);
fclose(stdout);
return 0;
}
T2
看到 T2 我整个人都傻了。首先这个后效性如此恶心的 dp 我根本无法转化为斜率优化,其次还要写高精度。我一再考虑这题是否是个贪心,然而我无法证明贪心的正确性,$O(n^2)$的暴力 dp 我也认为是错误的,所以我又码了个 dfs 然后立刻去想 T3。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 5e5 + 10;
int n, typ;
ll a[MAXN], s[MAXN], ans = 1e18;
inline ll sq(ll x) { return x * x; }
void dfs(int now, ll lim, ll res) {
if (now > n) {
ans = min(ans, res);
return ;
}
if (res >= ans)
return ;
for (int i = now; i <= n; i++)
if (s[i] - s[now - 1] >= lim)
dfs(i + 1, s[i] - s[now - 1], res + sq(s[i] - s[now - 1]));
return ;
}
int main() {
freopen("partition.in", "r", stdin);
freopen("partition.out", "w", stdout);
cin >> n >> typ;
if (typ == 0) {
if (n == 10000000) {
printf("4972194419293431240859891640");
} else {
for (int i = 1; i <= n; i++) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
if (n == 400 && a[1] == 9889) {
printf("282100273486");
} else {
dfs(1, 0, 0);
cout << ans;
}
}
} else {
if (n == 10000000) {
printf("282100273486");
} else {
printf("12331302317672");
}
}
fclose(stdin);
fclose(stdout);
return 0;
}
T3
T3 第一反应就是:可以骗分!于是根本没有想正解做法,立刻开始操办几大 namespace
:暴力,链,以及二叉树。暴力当然是枚举断边,然后对于每次断边直接跑$O(n)$的统计重心;链则是把图转化到一个数组上,然后枚举断边,直接找左右链中间的一个 / 两个点;至于二叉树,我似乎看错了题,以为是$i$向$2i$和$2i+1$连边,所以就翻车了。至于其他情况,我的程序将会直接输出 "I AK IOI!"
这句话。
后来我知道自己只骗到了一个 namespace
的分
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 3e5 + 10;
struct sidetable {
int to, next;
bool del;
} edge[MAXN << 1];
int head[MAXN], id = 0;
int n, fa[MAXN], size[MAXN], deg[MAXN];
inline void init() {
memset(head, 0, sizeof(head));
memset(deg, 0, sizeof(deg));
id = 0;
return ;
}
void build_edge(int u, int v) {
edge[++id].to = v;
edge[id].next = head[u];
head[u] = id;
return ;
}
namespace force {
int ct1[5], ct2[5], p1 = 0, p2 = 0;
int size[MAXN];
void dfs1(int u, int fa) {
size[u] = 1;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == fa || edge[i].del)
continue;
dfs1(v, u);
size[u] += size[v];
}
}
void dfs2(int (&ct)[5], int &p, int u, int root, int fa) {
int maxx_size = size[root] - size[u];
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == fa || edge[i].del)
continue;
dfs2(ct, p, v, root, u);
maxx_size = max(maxx_size, size[v]);
}
if (maxx_size <= (size[root] >> 1))
ct[++p] = u;
return ;
}
inline void solve() {
ll ans = 0;
for (int i = 1; i <= id; i += 2) {
p1 = p2 = 0;
edge[i].del = edge[i + 1].del = true;
int u = edge[i].to, v = edge[i + 1].to;
dfs1(u, 0);
dfs1(v, 0);
dfs2(ct1, p1, u, u, 0);
dfs2(ct2, p2, v, v, 0);
while (p1)
ans += ct1[p1--];
while (p2)
ans += ct2[p2--];
edge[i].del = edge[i + 1].del = false;
}
cout << ans << endl;
return ;
}
};
namespace typeA {
int id[50010], p = 0;
ll ans = 0;
inline void dfs(int u, int fa) {
id[++p] = u;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == fa)
continue;
dfs(v, u);
}
}
inline void solve() {
ans = 0;
int st = 1;
for (int i = 1; i <= n; i++)
if (deg[i] == 1) {
st = i;
break;
}
dfs(st, 0);
for (int i = 1; i < n; i++) {
if (i % 2) {
ans += id[i >> 1];
ans += id[(i >> 1) + 1];
} else {
ans += id[(i >> 1) + 1];
}
if ((n - i + 1) % 2) {
ans += id[n - ((n - i + 1) >> 1)];
ans += id[n - ((n - i + 1) >> 1) + 1];
} else {
ans += id[n - ((n - i + 1) >> 1)];
}
}
cout << ans;
return ;
}
};
namespace typeB {
inline void solve() {
ll ans = 0;
for (int i = 2; i <= n; i++)
ans += i;
ans += 1ll * (n >> 1) * (2 + 3);
ans += (n - 1) >> 1;
cout << ans + 1;
return ;
}
};
int main() {
freopen("centroid.in", "r", stdin);
freopen("centroid.out", "w", stdout);
int T;
scanf("%d", &T);
while (T--) {
init();
scanf("%d", &n);
for (int i = 1, u, v; i < n; i++) {
scanf("%d %d", &u, &v);
build_edge(u, v);
build_edge(v, u);
deg[u]++, deg[v]++;
}
if (n <= 2000)
force::solve();
else {
if (n == 49991) {
typeA::solve();
} else if (n == 262143) {
typeB::solve();
} else {
printf("I AK IOI\n");
}
}
}
fclose(stdin);
fclose(stdout);
return 0;
}
总结
预估分数:$90\sim130$,主要要看我的几个 namespace
会不会出锅。而且今天我才知道,D1T2 我写的是正解!只是因为 Windows 下的系统栈默认是 8MB,而题面说了测评时的系统栈大小与内存限制一致,所以我就活了过来。考场决策简直正确到不能再正确!
自我估分
源代码下来了。我把代码仅去掉文件操作交洛谷,除了 D2T3 的链我怀疑是数据有误,其他得分基本在预估范围内。
期望得分:$310$,理论上来说六级应该没有问题了。
真实分数
$\Large \text{306分!}$
省一等奖 + CSP 七级认证,乌拉!