NEERC 2016【杂题】
A Abbreviation
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
using namespace std;
vector <string> vr;
int N; string s;
inline bool big(char c) { return c >= 'A' && c <= 'Z'; }
inline bool small(char c) { return c >= 'a' && c <= 'z'; }
inline bool chk(string s) {
if (!big(s[0]) || s.size() <= 1) return 0;
for (int i = 1; i < s.size(); i++) if (!small(s[i])) return 0;
return 1;
}
inline void work() {
N = vr.size();
for (int i = 0; i < N; i++) {
int cur = i;
if (chk(vr[i]) && i != N - 1 && vr[i + 1] == " " && chk(vr[i + 2])) {
int j = i;
for (; j < N; j++) {
if ((j - i) % 2 == 0) {
if (!chk(vr[j])) { j--; break; }
else cout << vr[j][0];
}
else if (vr[j] != " ") break;
}
cout << " (";
for (int k = i; k < j - 1; k += 2) cout << vr[k] << ' ';
cout << vr[j - 1] << ')';
cur = j - 1;
} else {
cout << vr[i];
}
i = cur;
}
}
signed main(void) {
while (getline(cin, s)) {
if (s.size() == 0) break;
string w = "";
for (int i = 0; i < s.size(); i++) {
if (big(s[i]) || small(s[i])) w += s[i];
else {
vr.pb(w), w = "";
w += s[i];
vr.pb(w), w = "";
}
}
vr.pb(w);
work();
vr.clear();
cout << endl;
}
return 0;
}
B Binary Code
给定 \(n\) 个 \(01\) 串,每个字符串至多有一位未知。求一种方案使得任意一个字符串不是其它任意一个字符串的前缀。需判断无解。
\(n,\sum |S| \leq 5 \times 10^5\)。
solution
由于每个字符串至多有两种状态,考虑 2-SAT。然而直接暴力建边是 \(O(n^2)\) 的。
因为限制和前缀有关,考虑建出 Trie 树,每个点相当于要向子树内所有点和所有祖先连边,前缀优化建图即可。时间复杂度 \(O(n \log n)\)。
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int MN = 2e6 + 5;
const int MM = 2e7 + 5;
int N, M, a[2][MN], p[MN][2], ed[MN][2];
string s[MN], ans[MN][2];
int ch[MN][2], tot;
vector <int> vr[MN];
inline void ins(int *s, int L, int id) {
int p = 1;
for (int i = 1; i <= L; i++) {
int c = s[i];
if (!ch[p][c]) ch[p][c] = ++tot;
p = ch[p][c];
}
vr[p].pb(id);
}
vector <int> e[MM];
inline void add(int u, int v) {
if (u && v) e[u].pb(v);
}
inline void build(int u, int pr) {
int lst = ed[pr][0];
for (int v : vr[u]) {
p[v][0] = ++M;
add(v, lst), add(p[v][0], lst), add(p[v][0], v ^ 1);
lst = p[v][0];
}
ed[u][0] = lst, lst = ++M;
for (int i = 0; i < 2; i++) {
int v = ch[u][i];
if (v) build(v, u), add(lst, ed[v][1]);
}
reverse(vr[u].begin(), vr[u].end());
for (int v : vr[u]) {
p[v][1] = ++M;
add(v, lst), add(p[v][1], lst), add(p[v][1], v ^ 1);
lst = p[v][1];
}
ed[u][1] = lst;
}
int dfc, dfn[MM], low[MM], instk[MM], tp, stk[MM], bcnt, bel[MM];
inline void dfs(int u) {
dfn[u] = low[u] = ++dfc;
instk[u] = 1, stk[++tp] = u;
for (int v : e[u]) {
if (!dfn[v]) {
dfs(v);
low[u] = min(low[u], low[v]);
} else if (instk[v]) low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
int x = -1;
bcnt++;
while (x != u) {
x = stk[tp--];
bel[x] = bcnt;
instk[x] = 0;
}
}
}
signed main(void) {
scanf("%d", &N);
M = N << 1 | 1;
tot = 1;
for (int i = 1; i <= N; i++) {
cin >> s[i], ans[i][0] = ans[i][1] = s[i];
int L = s[i].size();
int fl = 0;
for (int j = 0; j < L; j++) {
if (s[i][j] == '?') {
for (int o = 0; o < 2; o++) a[o][j + 1] = o, ans[i][o][j] = '0' + o;
fl = 1;
} else {
a[0][j + 1] = a[1][j + 1] = s[i][j] - '0';
}
}
ins(a[0], L, i << 1), ins(a[1], L, i << 1 | 1);
if (!fl)
add(i << 1, i << 1 | 1);
}
build(1, 0);
for (int i = 1; i <= M; i++) if (!dfn[i]) dfs(i);
for (int i = 1; i <= N; i++)
if (bel[i << 1] == bel[i << 1 | 1]) return puts("NO"), 0;
puts("YES");
for (int i = 1; i <= N; i++)
cout << (bel[i << 1] < bel[i << 1 | 1] ? ans[i][0] : ans[i][1]) << endl;
return 0;
}
C Cactus Construction
有 \(n\) 个集合,初始第 \(i\) 个集合只包含第 \(i\) 个点,每个点的初始颜色都为 \(1\)。要求使用下列 \(3\) 种操作构建出给定的仙人掌(\(n\) 个点 \(m\) 条边)。
- 操作 \(1\):将 \(a\) 所在的集合和 \(b\) 所在的集合合并成一个集合。
- 操作 \(2\):将 \(a\) 所在的集合中,所有颜色为 \(c_1\) 点的颜色改为 \(c_2\)。
- 操作 \(3\):将 \(a\) 所在的集合中,在每一个颜色为 \(c_1\) 的点和每一个颜色为 \(c_2\) 的点之间连边,不允许出现重边。
其中 \(c_1,c_2 \in \{1,2,3,4\}\)。要求输出方案,操作数不多于 \(10^6\)。
\(n,m \leq 5 \times 10^4\)。
solution
首先考虑树怎么做,我们用 DFS 往上合并,令 \(3\) 种颜色分别代表每个点的 \(3\) 种状态:
- 颜色 \(1\):与这个点相邻的边均已建好。
- 颜色 \(2\):以这个点为根的整个子树都已建好,但和父亲还未连边。
- 颜色 \(3\):以这个点为根的整个子树还没建好。
假设当前考虑到点 \(u\),要将它和儿子 \(v\) 连边,显然此时其所有儿子都是颜色 \(3\),儿子子树内的点都是颜色 \(1\)。故可以按如下操作进行:先合并 \(u,v\),然后连接颜色 \(2\) 和 \(3\) 的点,最后将颜色 \(2\) 改为颜色 \(1\)。
考虑完所有儿子后将 \(u\) 的颜色 \(3\) 改为颜色 \(2\) 即可,这样我们只用 \(3\) 种颜色就能弄出一颗树了。
接下来考虑仙人掌怎么构造,先找出一棵 DFS 树,那么每条边只被至多一个非树边覆盖,并且每条非树边均为返祖边。于是考虑利用颜色 \(4\) 表示还未连返祖边的点。那么在考虑点 \(u\) 时,其所有儿子 \(v\) 一共有三种情况:
- \(v\) 不在环中或 \(v\) 所在的环顶端深度大于 \(u\):此时子树中必然不存在颜色 \(4\) 的点,按树的方法做就行了。
- \(v\) 在环中,且 \(v\) 所在环的顶端深度等于 \(u\):先桉树连边,然后把 \(u\) 连向所有颜色为 \(4\) 的点,再把所有颜色为 \(4\) 的点变为 \(1\)。
- \(v\) 在环中,且 \(v\) 所在环的顶端深度小于 \(u\):此时 \(v\) 的颜色应该被设为 \(4\),但为了防止干扰前一种情况,我们把这种情况留到最后处理。具体来说可以把 \(u\) 的所有儿子按照所在环的顶端深度从大到小排序,这样就能保证正确性了。
然后就做完了,操作数是 \(O(n)\) 的,可以轻松通过。
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int MN = 1e6 + 5;
int N, M, bak;
char vr[MN][20];
vector <int> e[MN];
int dep[MN], low[MN], cir[MN];
vector <int> son[MN], dfa;
inline void dfs(int u, int pr) {
low[u] = dep[u] = dep[pr] + 1;
for (int v : e[u]) {
if (v == pr) continue;
if (!dep[v]) {
son[u].pb(v);
dfs(v, u);
low[u] = min(low[u], low[v]);
} else if (dep[v] < dep[u]) {
low[u] = min(low[u], dep[v]), cir[u] = 1;
}
}
dfa.pb(u);
}
inline void solve() {
for (int u : dfa) {
sprintf(vr[++bak], "r %lld 1 3", u);
sort(son[u].begin(), son[u].end(), [&](const int &i, const int &j) {
return low[i] > low[j];
});
for (int v : son[u]) {
sprintf(vr[++bak], "j %lld %lld", u, v);
sprintf(vr[++bak], "c %lld 2 3", u);
if (low[v] < dep[u] && cir[v]) {
sprintf(vr[++bak], "r %lld 2 4", v);
} else {
if (low[v] == dep[u]) {
sprintf(vr[++bak], "c %lld 3 4", u);
sprintf(vr[++bak], "r %lld 4 1", u);
}
sprintf(vr[++bak], "r %lld 2 1", v);
}
}
sprintf(vr[++bak], "r %lld 3 2", u);
}
}
signed main(void) {
scanf("%lld %lld", &N, &M);
for (int i = 1; i <= M; i++) {
int k;
scanf("%lld", &k);
vector <int> p;
while (k--) {
int x = read();
p.pb(x);
}
for (int j = 0; j < p.size() - 1; j++)
e[p[j]].pb(p[j + 1]), e[p[j + 1]].pb(p[j]);
}
dfs(1, 0);
solve();
printf("%lld\n", bak);
for (int i = 1; i <= bak; i++)
printf("%s\n", vr[i]);
return 0;
}
D Delight for a Cat
有 \(n\) 天,每天能吃饭、睡觉。每天吃饭的愉悦值为 \(e_i\),睡觉的愉悦值为 \(s_i\),要求每连续 \(k\) 天都要有至少 \(E\) 天吃饭,\(S\) 天睡觉。求最大愉悦值。
\(k \leq n \leq 10^3\),\(0 \leq s_i,e_i \leq 10^9\),\(E+S \leq k\)。
solution
前置知识:区间选择模型
首先做个小转化:先强制每个小时都睡觉,然后可以选择一些小时改成吃饭,连续 \(k\) 个小时中吃饭的数量要在 \([m_e,k-m_s]\) 之中。
再做个小转化,将区间 \([i-k+1,i]\) 挂在点 \(i\), “第 \(i\) 小时改吃饭” 看成一个区间 \([i,i+k)\),那么上面的条件就等价于对于所有 \(i\ge k\) 的点有 \(i\) 被这些区间覆盖的次数在 \([m_e,k-m_s]\) 之中。
套用模型,令 \(c_i = 1\),\(a_i = m_e\),\(X = b_i = k - m_s\),\(w_i = s_i - e_i\) 即可。
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
const LL inf = 1e18;
int n, K, mS, mE;
int s[N], e[N];
int S, T;
int hd[N], nxt[N], to[N], w[N], flow[N], id[N], tot = 1;
inline void add(int u, int v, int fl, int _w) {
nxt[++tot] = hd[u], hd[u] = tot, to[tot] = v;
flow[tot] = fl, w[tot] = _w;
}
inline void adde(int u, int v, int fl, int _w) {
add(u, v, fl, _w);
add(v, u, 0, -_w);
}
queue <int> q;
int pre[N]; LL d[N];
bool vis[N];
inline bool spfa() {
for (int i = 1; i <= T; i++) vis[i] = 0, d[i] = inf;
vis[S] = 1, d[S] = 0, q.push(S);
while (!q.empty()) {
int u = q.front(); q.pop(); vis[u] = 0;
for (int i = hd[u]; i; i = nxt[i]) {
int v = to[i];
if (d[v] > d[u] + w[i] && flow[i]) {
d[v] = d[u] + w[i], pre[v] = i;
if (!vis[v]) vis[v] = 1, q.push(v);
}
}
}
return d[T] < inf;
}
inline LL upd() {
int fl = 1e9; LL ret = 0;
for (int i = pre[T]; i; i = pre[to[i ^ 1]]) fl = min(fl, flow[i]);
for (int i = pre[T]; i; i = pre[to[i ^ 1]]) {
flow[i] -= fl, flow[i ^ 1] += fl;
ret += 1ll * fl * w[i];
}
return ret;
}
inline LL mcf() {
LL ans = 0;
while (spfa()) ans += upd();
return ans;
}
signed main(void) {
cin >> n >> K >> mS >> mE;
LL sum = 0;
for (int i = 1; i <= n; i++) cin >> s[i], sum += s[i];
for (int i = 1; i <= n; i++) cin >> e[i], e[i] -= s[i];
int mi = mE, mx = K - mS;
int S0 = n + 1;
S = n + 2, T = n + 3;
adde(S, S0, mx, 0);
for (int i = 1; i <= n; i++) {
if (i <= K) adde(S0, i, 1e9, 0);
if (i + 1 <= n) adde(i, i + 1, mx - mi, 0);
else adde(i, T, mx - mi, 0);
if (i + K <= n) adde(i, i + K, 1, -e[i]);
else adde(i, T, 1, -e[i]);
id[i] = tot - 1;
}
cout << sum - mcf() << endl;
for (int u = 1; u <= n; u++) printf(flow[id[u]] ? "S" : "E");
return 0;
}
E Expect to Wait
有 \(n\) 个事件形如第 \(t_i\) 天 \(a_i\) 个人来借车(如果借不到就会一直等)或还车。\(q\) 次询问当初始有 \(b_i\) 辆车时所有人的等待时间之和,如果最后还有人没借到车输出 \(-1\)。
\(n,q \leq 10^5\),\(t_i,b_i \leq 10^9\),\(a_i \leq 10^4\)。
solution
判断无解是容易的,不妨只考虑有解的情况。以时间为横轴、车数为纵轴画出一条折线,可以发现所有人的等待时间之和恰好是折线在横轴下方的部分和横轴围成的图形的面积。
考虑如何处理多次询问:先算出 \(b_i =0\) 时的答案,此时折线在横轴下方的部分和横轴所围成的图形可以拆分成若干高度不同的矩形。当 \(b_i > 0\) 时我们需要维护这些矩形向上平移 \(b_i\) 后的答案,将矩形按高度排序,询问按 \(b_i\) 排序后用指针维护一下即可。时间复杂度 \(O(n \log n)\)。
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5;
struct node {
int x, id;
bool operator < (const node &b) const {
return x < b.x;
}
} p[N];
int n, m, q, a[N], s[N], t[N];
int vr[N], bak;
LL s1, s2, ans[N];
signed main(void) {
cin >> n >> q;
for (int i = 1; i <= n; i++) {
char ch;
cin >> ch >> t[i] >> a[i];
a[i] *= (ch == '+' ? 1 : -1);
}
for (int i = 1; i <= n; i++) {
s[i] = s[i - 1] + a[i];
if (i < n) t[i] = t[i + 1] - t[i];
}
for (int i = 1; i <= n; i++) if (s[i] < 0) {
vr[++bak] = i;
s1 += 1ll * t[i] * (-s[i]);
s2 += t[i];
}
sort(vr + 1, vr + bak + 1, [&](const int &i, const int &j) {
return s[i] > s[j];
});
for (int i = 1; i <= q; i++) cin >> p[i].x, p[i].id = i;
sort(p + 1, p + q + 1);
int j = 1;
for (int i = 1; i <= q; i++) {
while (j <= bak && -s[vr[j]] <= p[i].x) {
s1 -= 1ll * t[vr[j]] * -s[vr[j]];
s2 -= t[vr[j]];
j++;
}
ans[p[i].id] = (s[n] + p[i].x < 0 ? -1 : s1 - 1ll * p[i].x * s2);
}
for (int i = 1; i <= q; i++)
ans[i] == -1 ? puts("INFINITY") : printf("%lld\n", ans[i]);
return 0;
}
F Foreign Postcards
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MN = 1e6 + 5;
int N, n0, n1; double f[MN], suf;
char s[MN];
signed main(void) {
scanf("%s", s + 1);
N = strlen(s + 1);
for (int i = N; i >= 1; i--) {
(s[i] == 'C' ? n1 : n0) += (N - i + 1);
f[i] = 1 / (1.0 * (double)(N - i + 1)) * (suf + (double)(s[i] == 'C' ? n0 : n1));
suf += f[i];
}
printf("%.12lf\n", f[1]);
return 0;
}
G Game on Graph
Alice 和 Bob 在一个 \(n\) 个点 \(m\) 条边的有向图上玩游戏,两人轮流操作,每次可以将棋子沿着其中一条边移动,不能移动者输。对于每个点,分别求出以这个点为起点开始游戏,两人分别作为先手,最终会输,赢,还是平局。
其中 Alice 希望平局,而 Bob 不希望平局。在不平局的基础上,两人都更希望赢。
\(n \leq 10^5\),\(m \leq 2 \times 10^5\)。
solution
先在反图上用类似拓扑排序的方式转移出所有非平局的状态,剩下的状态一定会平局。然后对于非平局的状态也用类似的方式对于每个点求出:Alice 先手时是否必败、Bob 先手时是否必胜。
剩下没转移到的状态一定满足:它们既不能平局,Bob 也没法赢,那 Bob 一定会跑去紫砂,于是这些点一定都是 Alice 胜利。时间复杂度 \(O(n+m)\)。
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pb push_back
#define pii pair <int, int>
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int N = 2e5 + 5;
int n, m, deg[N], d[N];
int ud[N][2], sg[N][2];
vector <int> ie[N];
queue <pii> q;
signed main(void) {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v; cin >> u >> v;
ie[v].pb(u), deg[u]++;
}
memcpy(d, deg, sizeof(d));
for (int i = 1; i <= n; i++) if (!deg[i]) {
ud[i][0] = ud[i][1] = 1;
q.push(mp(i, 0)), q.push(mp(i, 1));
}
while (!q.empty()) {
int u = q.front().fi, w = q.front().se; q.pop();
for (int v : ie[u]) {
if (!w && !ud[v][1]) {
ud[v][1] = 1;
q.push(mp(v, 1));
}
else if (w == 1) {
if (!(--d[v]))
ud[v][0] = 1, q.push(mp(v, 0));
}
}
}
memcpy(d, deg, sizeof(d));
for (int i = 1; i <= n; i++) {
if (!deg[i]) sg[i][0] = 1, q.push(mp(i, 0));
if (!ud[i][0]) sg[i][0] = -1;
if (!ud[i][1]) sg[i][1] = -1;
}
while (!q.empty()) {
int u = q.front().fi, w = q.front().se; q.pop();
for (int v : ie[u]) {
if (!w && !sg[v][1]) {
sg[v][1] = 1;
q.push(mp(v, 1));
}
else if (w == 1 && !sg[v][0]) {
if (!(--d[v]))
sg[v][0] = 1, q.push(mp(v, 0));
}
}
}
for (int i = 1; i <= n; i++)
putchar(!ud[i][0] ? 'D' : (!sg[i][0] ? 'W' : 'L'));
puts("");
for (int i = 1; i <= n; i++)
putchar(sg[i][1] > 0 ? 'W' : (ud[i][1] ? 'L' : 'D'));
return 0;
}
H Hard Refactoring
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
string s, t;
int n, v, id;
int mi = -32768, mx = 32767;
struct node {
int L, R;
node() : L(mi), R(mx) {}
node(int l, int r) : L(l), R(r) {}
bool operator < (const node &b) const {
return L == b.L ? R > b.R : L < b.L;
}
} a[N];
set <node> ss;
signed main(void) {
while (1) {
getline(cin, s);
stringstream ss(s);
int fl = 0, lb = mi, rb = mx;
while (ss >> t) {
if (t == ">=") ss >> v, lb = v;
if (t == "<=") ss >> v, rb = v;
if (t == "||") fl = 1;
}
if (lb <= rb && lb <= mx && rb >= mi) {
n++;
a[n].L = max(a[n].L, lb);
a[n].R = min(a[n].R, rb);
}
if (!fl) break;
}
if (!n) return puts("false"), 0;
sort(a + 1, a + n + 1);
int l = a[1].L, r = a[1].R;
for (int i = 2; i <= n; i++) {
if (a[i].L > r + 1)
ss.insert(node(l, r)), l = a[i].L, r = a[i].R;
else
r = max(r, a[i].R);
}
ss.insert(node(l, r));
if (l == mi && r == mx) return puts("true"), 0;
int cnt = 0;
for (auto it = ss.begin(); it != ss.end(); it++) {
node ns = *it;
cnt++;
if (ns.L == mi)
printf("x <= %d", ns.R);
else if (ns.R == mx)
printf("x >= %d", ns.L);
else
printf("x >= %d && x <= %d", ns.L, ns.R);
if (cnt != ss.size()) printf(" ||\n");
else break;
}
return 0;
}
I Indiana Jones and the Uniform Cave
交互题。
solution
考虑如何在只知道当前节点状态的情况下进行 dfs 的过程。
我们用三种标记表示节点的三种状态:\(c\) 表示这个节点还没访问过,\(r\) 表示在当前栈中,\(l\) 表示不在栈中。对于 \(r\) 节点,每次将石头放在当前访问边上,当前边被访问完之后将石头往后挪一格,\(m\) 条边都访问完之后进行回溯。
但是现在的问题在于我们其实并不能简单地进行回溯。
假设当前点为 \(u\),当前边到达了一个 \(r\) 节点 \(v\),这种情况比较简单,只需要把 \(v\) 先改成 \(l\),然后绕一圈就能知道这一圈上有 \(c\) 个节点,再从 \(v\) 开始走 \(c-1\) 就能走到 \(u\) 的父亲。注意要把 \(v\) 改回 \(r\)。
如果到了一个 \(l\) 节点,那么我们希望能够尽量让它往回跳,如果跳到了一个 \(r\) 点就可以按刚刚的方法做了。类似 tarjan 那样做就行了。由于图强连通,所以一定能跳回去。
每条边最多走 \(2n\) 步,共 \(nm\) 条边,回溯至多 \(n\) 步,所以至多走 \(2n^2m + n^2\) 步,足以通过此题。
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 5;
const int inf = 1e9;
char str[2][10] = { "left", "right" }, ch[10];
char go(int a, char ty, int b) {
printf("%d %s %d\n", a, str[ty == 'l' ? 0 : 1], b);
fflush(stdout);
scanf("%s", ch);
if (ch[0] == 't') exit(0);
return ch[0];
}
int walk(char ty, int x = inf) {
char cur = ty; int c = 0;
for (; cur == ty && c < x; c++) cur = go(0, ty, 0);
return c;
}
int m, dfc, lc[N];
int dfs() {
int u = ++dfc, lid = -1;
for (int i = 0; i < m; i++) {
char nxt = go(1, 'r', 1);
int c = 0;
if (nxt == 'c') {
int v = dfs();
c = lc[v] - 1;
} else if (nxt == 'r') {
if (go(0, 'l', 0) == 'r') c = walk('r'), walk('r', c);
else c = 0, go(0, 'r', 0);
} else {
walk('l'), c = walk('r') - 1;
walk('l'), walk('r', c);
}
if (c > lc[u])
lid = (i + 1) % m, lc[u] = c;
}
if (u != 1) {
char cur = go(lid, 'l', lid);
if (cur == 'l') walk('l');
walk('r', lc[u] - 1);
}
return u;
}
int main(void) {
scanf("%d%s", &m, ch);
dfs();
return 0;
}
J Jenga Boom
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e4 + 5;
int n, m, w, h, l[N], r[N]; bool era[N][N];
LL s[N], c[N];
inline bool chk() {
LL cnt[2] = {0, 0}, sum[2] = {0, 0};
for (int i = h, cur = h & 1; i > 1; i--, cur ^= 1) {
cnt[cur] += c[i], sum[cur] += s[i];
if ((sum[cur ^ 1] + cnt[cur] * n) <= (cnt[0] + cnt[1]) * (2 * l[i - 1] - 2)) return 1;
if ((sum[cur ^ 1] + cnt[cur] * n) >= (cnt[0] + cnt[1]) * (2 * r[i - 1])) return 1;
}
return 0;
}
int main(void) {
cin >> n >> w >> h >> m;
for (int i = 1; i <= h; i++)
l[i] = 1, r[i] = n, c[i] = n, s[i] = 1ll * n * n;
for (int i = 1; i <= m; i++) {
int x, y;
cin >> x >> y;
era[x][y] = 1, s[x] -= 2 * y - 1;
if (--c[x]) {
while (era[x][l[x]]) l[x]++;
while (era[x][r[x]]) r[x]--;
} else {
if (x == h) h--;
else return printf("yes\n%d\n", i), 0;
}
if (!h || chk())
return printf("yes\n%d\n", i), 0;
}
puts("no");
return 0;
}
K Kids Designing Kids
给定三个 \(\text{01}\) 矩阵,求是否存在一种平移方式使得异或后为 \(0\),并构造方案。
\(n,m \leq 10^3\)。
solution
重要的观察是,考虑每个矩形最上面的 \(1\) 中最左边的一个,如果存在合法方案,那么一定有两个矩形的这两个位置是重合的。证明考虑反证,若都不重合,考虑这三个点构成的图形中最上面的 \(1\) 中最左边的一个,其必然不可能被其它的 \(1\) 所覆盖,所以在最终的图形中必然为 \(1\)。
因此可能的方案数只有 \(O(1)\) 种,暴力 check 就行了。
偷懒的写法是用 set 维护每个矩阵中为 \(1\) 的位置,时间复杂度 \(O(nm \log nm)\),开 O2 能过。
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair <int, int>
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int N = 1e3 + 5;
pii d1, d2, d3;
set <pii> s1, s2, s3, s;
pii operator + (const pii &x, const pii &y) {
return mp(x.fi + y.fi, x.se + y.se);
}
inline void in(set <pii> &s, pii &d) {
int n, m; char ch[N][N];
cin >> n >> m;
int fl = 0;
for (int i = 1; i <= n; i++) scanf("%s", ch[i] + 1);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) if (ch[i][j] == '*') {
if (!fl) d = mp(-i, -j), fl = 1;
s.insert(mp(i, j) + d);
}
}
inline bool chk(set <pii> &s1, set <pii> &s2, set <pii> &s3, pii &d3) {
s.clear();
for (pii i : s1) s.insert(i);
for (pii i : s2) {
if (s.find(i) != s.end()) s.erase(i);
else s.insert(i);
}
pii d = *s.begin();
if (s3.size() != s.size()) return 0;
for (pii i : s3) if (s.find(i + d) == s.end()) return 0;
d3 = d3 + d;
return 1;
}
signed main(void) {
in(s1, d1), in(s2, d2), in(s3, d3);
if (chk(s1, s2, s3, d3) || chk(s1, s3, s2, d2) || chk(s2, s3, s1, d1)) printf("YES\n%d %d\n", d2.se - d1.se, d2.fi - d1.fi);
else puts("NO");
return 0;
}
L List of Primes
将质数集合的所有子集按照子集和为第一关键字,字典序为第二关键字从小到大排序,求最终形成的字符串的第 \(l\sim r\) 个字符。
\(1 \leq l \leq r \leq 10^{18}\),\(r - l \leq 10^5\)。
solution
预处理 \(f_{i,j}\) 和 \(g_{i,j}\) 分别表示只用第 \(j\) 个及以后的质数拼出来的和为 \(i\) 的子集个数,及所有这样的子集的长度之和(不考虑两边的中括号和逗号)。然后嗯爆搜就行了。
复杂度不会分析,有题解说是 \(O(r - l)\) 的,我暂且蒙古。
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2100 + 5;
const int M = 350;
int cnt, vis[N], pr[N], w[N];
LL f[N][M], g[N][M];
void init() {
for (int i = 2; i < N; i++) if (!vis[i]) {
pr[++cnt] = i, f[i][cnt] = 1, g[i][cnt] = w[i] = log10(i) + 1;
for (int j = 2 * i; j < N; j += i) vis[j] = 1;
}
for (int i = cnt; i; i--) {
for (int j = pr[i]; j < N; j++) {
f[j][i] += f[j - pr[i]][i + 1] + f[j][i + 1];
g[j][i] += g[j - pr[i]][i + 1] + g[j][i + 1] + f[j - pr[i]][i + 1] * (w[pr[i]] + 2);
}
}
}
LL l, r, cur; int p[N];
string trans(int x) {
string s;
while (x) s += x % 10 + '0', x /= 10;
reverse(s.begin(), s.end());
return s;
}
void add(char s) {
if (++cur > r) exit(0);
if (cur >= l) cout << s;
}
void dfs(int s, int c, int lb, LL sl) {
if (!s) {
add('[');
for (int i = 1; i <= c; i++) {
string str = trans(p[i]);
for (int j = 0; j < str.size(); j++) add(str[j]);
if (i < c) add(','), add(' ');
}
add(']'), add(','), add(' ');
return;
}
LL nxt = cur + (c * 2 + 4 + sl) * f[s][lb] + g[s][lb];
if (nxt < l) { cur = nxt; return; }
c++;
for (int j = lb; j <= cnt; j++) {
int res = s - pr[j]; p[c] = pr[j];
if (res < 0) break;
if (res == 0 || f[res][j]) dfs(res, c, j + 1, sl + w[pr[j]]);
}
}
int main(void) {
init();
cin >> l >> r;
for (int i = 2; i < N; i++) dfs(i, 0, 1, 0);
return 0;
}
M Mole Tunnels
有一棵 \(n\) 个节点的完全二叉树,第 \(i\) 个点最多能有 \(c_i\) 只鼹鼠。有 \(m\) 只鼹鼠依次醒来,第 \(i\) 只初始在 \(p_i\),对于 \(\forall k\in[1,m]\) 输出前 \(k\) 只鼹鼠醒来后移动长度总和的最小值。
\(n,m \leq 10^5\)。
solution
容易建出费用流模型:源点向 \(p_i\) 连容量为 \(1\), 费用为 \(0\) 的边。树上相邻两点连容量 \(+\infty\), 费用为 \(1\) 的双向边。有食物的点向汇点连容量为 \(c_i\),费用为 \(0\) 的边。但我们显然不能每次都做一遍。
考虑模拟费用流,每次新加一个点相当于要新找一条源点 \(\to\) 加入的点 \(\to\) 汇点的增广路,相当于需要在树上找到一条加入的点 \(\to\) 有食物的点的最短路,树形 DP 维护出 \(f_i,g_i\) 表示点 \(i\) 到其子树中有食物的点的最短路为多少、是哪个点即可通过跳 \(O(\log n)\) 个父亲找到这条路径。
双向边以及它们的反向边的流量以及费用可以维护一个 \(w_i\) 表示从 \(i\) 的父亲到 \(i\) 的正向边流量,当其为正时,父亲到儿子只能选择正向边,儿子到父亲优先选择反向边;当其为负时,儿子到父亲只能选择正向边,父亲到儿子优先选择反向边;当为 \(0\) 时,两者都没有流量,只能选择正向边。
时间复杂度 \(O(n \log n)\)。
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define mem(x, v) memset(x, v, sizeof(x))
using namespace std;
const int N = 1e5 + 5;
const int inf = 1e9;
int n, m, p[N], c[N], f[N << 1], g[N << 1];
int fl[N], ans;
inline void upd(int &a, int &b, int x, int y) {
if (x < a) a = x, b = y;
}
inline void upd(int x) {
f[x] = inf;
if (c[x]) f[x] = 0, g[x] = x;
upd(f[x], g[x], f[x << 1] + (fl[x << 1] < 0 ? -1 : 1), g[x << 1]);
upd(f[x], g[x], f[x << 1 | 1] + (fl[x << 1 | 1] < 0 ? -1 : 1), g[x << 1 | 1]);
}
int main(void) {
ios :: sync_with_stdio(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> c[i];
for (int i = 1; i <= m; i++) cin >> p[i];
mem(f, 0x3f);
for (int i = n; i; i--) upd(i);
for (int i = 1; i <= m; i++) {
int x = inf, y = 0, u = p[i], t = 0, v = 0;
while (u) {
if (x > f[u] + t) x = f[u] + t, y = g[u], v = u;
t += (fl[u] > 0 ? -1 : 1), u >>= 1;
}
u = p[i], ans += x;
while (u != v) fl[u]--, u >>= 1, upd(u);
c[y]--, upd(y);
while (y != v) fl[y]++, y >>= 1, upd(y);
while (v) upd(v), v >>= 1;
printf("%d ", ans);
}
return 0;
}