NOIP2018 解题笔记
D1T1 铺设道路
在场上并没有想到积木大赛这道原题。
差分之后可以把在$[l, r]$这段区间$ - 1$变成在$l$处$ - 1$,在$r + 1$处$ + 1$,然后最终目标是使$\forall i \in [1, n] \ \Delta d_i == 0$成立。就想着把正负数配一配对,然后输出了正数绝对值和负数绝对值的$max$,这导致了我的代码非常鬼畜。
出来之后发现正数绝对值一定大于等于负数绝对值,要不然就会出现$d_i < 0$的情况。
时间复杂度$O(n)$。
#include <cstdio> #include <cstring> using namespace std; typedef long long ll; const int N = 1e5 + 5; int n; ll a[N], b[N]; template <typename T> inline void read(T &X) { X = 0; char ch = 0; T op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } ll abs(ll x) { return x > 0 ? x : -x; } inline ll max(ll x, ll y) { return x > y ? x : y; } int main() { // freopen("road.in", "r", stdin); // freopen("road.out", "w", stdout); read(n); for(int i = 1; i <= n; i++) { read(a[i]); b[i] = a[i] - a[i - 1]; } /* for(int i = 1; i <= n; i++) printf("%lld ", b[i]); printf("\n"); */ ll ps = 0LL, ne = 0LL; for(int i = 1; i <= n; i++) { if(b[i] > 0) ps += b[i]; else ne -= b[i]; } printf("%lld\n", max(ne, ps)); return 0; }
D1T2 货币系统
一开始觉得是一道数学题,后来回过头来发现应该可以完全背包,先写了$80$分的暴力,然后又想了一下可以用分治优化到$O(Tnlogna_i)$,就写了一下然后拍了拍,最后在少爷机上$AC$了。
用$solve(l, r)$表示不考虑$[l, r]$这段区间内的货币的情况,然后在向下算的时候每一次做一半就好了。
出来之后发现就是个$sb$排序。
时间复杂度$O(Tna_i)$,放上分治的代码。
#include <cstdio> #include <cstring> using namespace std; const int N = 105; const int M = 25005; const int Lg = 20; int testCase, n, mx = 0, a[N], ans; bool f[Lg][M]; inline void read(int &X) { X = 0; char ch = 0; int op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } inline void chkMax(int &x, int y) { if(y > x) x = y; } void solve(int l, int r, int dep) { if(l == r) { if(f[dep][a[l]]) ans--; return; } ++dep; for(int i = 0; i <= mx; i++) f[dep][i] = f[dep - 1][i]; int mid = ((l + r) >> 1); for(int i = l; i <= mid; i++) for(int j = a[i]; j <= mx; j++) f[dep][j] |= f[dep][j - a[i]]; solve(mid + 1, r, dep); for(int i = 0; i <= mx; i++) f[dep][i] = f[dep - 1][i]; for(int i = mid + 1; i <= r; i++) for(int j = a[i]; j <= mx; j++) f[dep][j] |= f[dep][j - a[i]]; solve(l, mid, dep); } int main() { // freopen("money.in", "r", stdin); // freopen("money.out", "w", stdout); for(read(testCase); testCase--; ) { read(n); mx = 0; for(int i = 1; i <= n; i++) { read(a[i]); chkMax(mx, a[i]); } for(int i = 1; i <= mx; i++) f[0][i] = 0; f[0][0] = 1; ans = n; solve(1, n, 0); printf("%d\n", ans); } return 0; }
D1T3 赛道修建
挺简单的第三题。首先外层二分,然后把一个点的所有儿子存下来,贪心从小到大匹配,在保证对答案的贡献最大的情况下使延伸到父亲处理的链尽量长。我在考场上写了一个常数巨大的$multiset$,也能跑过。
其实也可以再二分一个能向上的最长链然后看看答案会不会变差就行了,这样子常数比较优秀。
仍然不会更高级的解法。
时间复杂度$O(nlog^2n)$。
#include <cstdio> #include <cstring> #include <algorithm> #include <set> using namespace std; const int N = 5e4 + 5; const int inf = 1 << 30; int n, m, cnt, lim, tot = 0, head[N], f[N]; multiset <int> s[N]; struct Edge { int to, nxt, val; } e[N << 1]; inline void add(int from, int to, int val) { e[++tot].to = to; e[tot].val = val; e[tot].nxt = head[from]; head[from] = tot; } inline void read(int &X) { X = 0; char ch = 0; int op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } inline void chkMax(int &x, int y) { if(y > x) x = y; } /* inline int getPos(int which, int siz, int val) { val = lim - val; int ln = 0, rn = siz, mid, res = -1; for(; ln <= rn; ) { mid = ((ln + rn) >> 1); if(vec[which][mid] >= val) res = mid, rn = mid - 1; else ln = mid + 1; } return res; } */ /* void dfs(int x, int fat) { vec[x].clear(); for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat) continue; dfs(y, x); vec[x].push_back(f[y] + e[i].val); } sort(vec[x].begin(), vec[x].end()); int vecSiz = vec[x].size() - 1; for(; vecSiz; --vecSiz) { if(vecSiz == -1) break; if(vec[x][vecSiz] < lim) break; ++cnt; } for(int i = 0; i <= vecSiz; i++) tag[i] = 0; for(int i = vecSiz; i > 0; i--) { int pos = getPos(x, i - 1, vec[x][i]); if(pos != -1) tag[pos] = 1, tag[i] = 1, ++cnt; } int res = 0; for(int i = 0; i <= vecSiz; i++) if(tag[i]) tag[i] = 0; else chkMax(res, vec[x][i]); f[x] = res; } */ void dfs(int x, int fat) { // s[x].clear(); for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat) continue; dfs(y, x); s[x].insert(f[y] + e[i].val); } for(; !s[x].empty(); ) { multiset <int> :: iterator it = (--s[x].end()); if((*it) >= lim) { ++cnt; s[x].erase(it); } else break; } int res = 0; for(; !s[x].empty(); ) { multiset <int> :: iterator p1 = s[x].begin(); int tmp = (*p1); s[x].erase(p1); multiset <int> :: iterator p2 = s[x].lower_bound(lim - tmp); if(p2 == s[x].end()) { chkMax(res, tmp); } else { cnt++; s[x].erase(p2); } } f[x] = res; } inline bool chk(int mid) { lim = mid, cnt = 0; for(int i = 1; i <= n; i++) f[i] = 0; dfs(1, 0); return cnt >= m; } int main() { // freopen("track.in", "r", stdin); // freopen("track.out", "w", stdout); read(n), read(m); int ln = 0, rn = 0, mid, res = inf; for(int x, y, v, i = 1; i < n; i++) { read(x), read(y), read(v); add(x, y, v), add(y, x, v); rn += v; } for(; ln <= rn; ) { mid = (ln + rn) / 2; if(chk(mid)) ln = mid + 1, res = mid; else rn = mid - 1; } printf("%d\n", res); return 0; }
D2T1 旅行
原来以为图论跑到$T1$来了,没想到最后还是个树。
先考虑树的部分分,发现只有走完一个点的所有子树之后才能向上走,所以每次贪心地走最小的子树就好了;然后考虑基环树的部分分,看到$n$不超过$5000$,直接把环找出来然后断一断走一走取个最小就好了。
场上写出了一个$bug$但只被卡了$4$分。
树的话时间复杂度是$O(nlogn)$,基环树是$O(n^2)$。
#include <cstdio> #include <cstring> #include <vector> #include <algorithm> using namespace std; const int N = 5005; const int inf = 1 << 30; int n, m, cnt = 0, ans[N]; vector <int> e[N]; inline void read(int &X) { X = 0; char ch = 0; int op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } namespace Solve1 { void dfs(int x, int fat) { int vecSiz = e[x].size(); for(int i = 0; i < vecSiz; i++) { int y = e[x][i]; if(y == fat) continue; ans[++cnt] = y; dfs(y, x); } } void work() { ans[++cnt] = 1; dfs(1, 0); for(int i = 1; i <= n; i++) { printf("%d", ans[i]); if(i == n) putchar('\n'); else putchar(' '); } } } namespace Solve2 { int top, stk[N], sum, cir[N], dx, dy, res[N]; bool inc[N], vis[N], flag = 0; void getCir(int x, int fat) { // int pre = top; if(flag) return; stk[++top] = x, vis[x] = 1; int vecSiz = e[x].size(); for(int i = 0; i < vecSiz; i++) { int y = e[x][i]; if(y == fat) continue; if(vis[y]) { flag = 1; sum = 0; for(; stk[top] != y; --top) { inc[stk[top]] = 1; vis[stk[top]] = 0; cir[++sum] = stk[top]; } inc[y] = 1, cir[++sum] = y, vis[y] = 0, top--; return; } else getCir(y, x); } // top = pre; if(vis[x]) --top, vis[x] = 0; } inline bool bet() { for(int i = 1; i <= n; i++) if(res[i] != ans[i]) return res[i] < ans[i]; return 0; } void dfs(int x, int fat) { int vecSiz = e[x].size(); for(int i = 0; i < vecSiz; i++) { int y = e[x][i]; if(y == fat) continue; if((x == dx && y == dy) || (x == dy && y == dx)) continue; res[++cnt] = y; dfs(y, x); } } void work() { top = 0; getCir(1, 0); /* for(int i = 1; i <= sum; i++) printf("%d ", cir[i]); printf("\n"); */ for(int i = 1; i <= n; i++) ans[i] = inf; for(int i = 1; i < sum; i++) { dx = cir[i], dy = cir[i + 1]; res[cnt = 1] = 1; dfs(1, 0); if(bet()) { for(int j = 1; j <= n; j++) ans[j] = res[j]; } } dx = cir[1], dy = cir[sum]; res[cnt = 1] = 1; dfs(1, 0); if(bet()) { for(int j = 1; j <= n; j++) ans[j] = res[j]; } for(int i = 1; i <= n; i++) { printf("%d", ans[i]); if(i == n) putchar('\n'); else putchar(' '); } } } int main() { // freopen("travel.in", "r", stdin); // freopen("travel.out", "w", stdout); // freopen("testdata.in", "r", stdin); read(n), read(m); for(int x, y, i = 1; i <= m; i++) { read(x), read(y); e[x].push_back(y), e[y].push_back(x); } for(int i = 1; i <= n; i++) sort(e[i].begin(), e[i].end()); if(m == n - 1) Solve1 :: work(); else Solve2 :: work(); return 0; }
D2T2 填数游戏
我到现在都还认为这是一道打表题。
放上大神的状压题解 戳这里
首先写个暴力写打出$(n, n)$的表$(n \leq 8)$,然后就靠这三条性质求答案:
1、$(n, m) = (m, n)$。
2、$(n, m) = (n, m - 1) * 3$ $(m > n)$。
3、$$ (n, n + 1) = \left\{\begin{matrix}
(n, n) * 3 & (n \leq 3) \\
(n, n) * 3 - 2^n * 3& (n \geq 4)
\end{matrix}\right. $$
时间复杂度$O(logn)$。
#include <cstdio> #include <cstring> using namespace std; typedef long long ll; const int N = 10; const ll P = 1e9 + 7; const ll base[N] = {0, 0, 12, 112, 912, 7136, 56768, 453504, 3626752, 0}; int n, m; template <typename T> inline void swap(T &x, T &y) { T t = x; x = y; y = t; } inline ll fpow(ll x, ll y) { ll res = 1LL; for(; y > 0; y >>= 1) { if(y & 1) res = res * x % P; x = x * x % P; } return res; } int main() { scanf("%d%d", &n, &m); if(n > m) swap(n, m); if(n == 1) return printf("%lld\n", fpow(2LL, m)), 0; if(n == m) return printf("%lld\n", base[n]), 0; ll ans = 1LL; if(n > 3) ans = (3LL * base[n] % P - 3LL * fpow(2LL, n) % P + P) % P; else ans = base[n] * 3LL % P; ans = ans * fpow(3LL, m - n - 1) % P; printf("%lld\n", ans); return 0; }
D2T3 保卫王国
为什么会有动态$dp$这种东西出现啊啊啊啊啊啊。
我还是只会倍增的做法。用$h_{x, 0/1}$表示$x$的子树中选/不选$x$的最小代价,用$g_{x, 0/1}$表示$x$到根的链上不算$x$的子树$x$选/不选的最小代价,用$f_{x, i, 0/1, 0/1}$表示从$x$向上跳$2^i$的这条链上不算$x$的子树其他子树的最小代价。
然后剩下看代码吧,感觉代码肯定比我讲得清楚。
时间复杂度$O(nlogn)$,常数巨大。
#include <cstdio> #include <cstring> #include <map> #include <algorithm> using namespace std; typedef long long ll; typedef pair <int, int> pin; const int N = 1e5 + 5; const int Lg = 20; const ll inf = 1LL << 60; int n, qn, tot = 0, head[N], fa[N][Lg], dep[N]; ll a[N], h[N][2], f[N][Lg][2][2], g[N][2]; map <pin, int> ex; struct Edge { int to, nxt; } e[N << 1]; inline void add(int from, int to) { e[++tot].to = to; e[tot].nxt = head[from]; head[from] = tot; } template <typename T> inline void read(T &X) { X = 0; char ch = 0; T op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } template <typename T> inline void chkMin(T &x, T y) { if(y < x) x = y; } inline ll min(ll x, ll y) { return x > y ? y : x; } void dp1(int x, int fat) { h[x][0] = 0, h[x][1] = a[x]; for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat) continue; dp1(y, x); h[x][0] += h[y][1]; h[x][1] += min(h[y][0], h[y][1]); } } void dp2(int x, int fat) { for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat) continue; g[y][0] = g[x][1] + h[x][1] - min(h[y][0], h[y][1]); g[y][1] = min(g[y][0], h[x][0] - h[y][1] + g[x][0]); dp2(y, x); } } void dfs(int x, int fat, int depth) { fa[x][0] = fat, dep[x] = depth; f[x][0][0][0] = inf, f[x][0][0][1] = h[fat][1] - min(h[x][0], h[x][1]); f[x][0][1][0] = h[fat][0] - h[x][1], f[x][0][1][1] = h[fat][1] - min(h[x][0], h[x][1]); for(int i = 1; i <= 18; i++) { fa[x][i] = fa[fa[x][i - 1]][i - 1]; for(int u = 0; u < 2; u++) for(int v = 0; v < 2; v++) { f[x][i][u][v] = inf; for(int k = 0; k < 2; k++) chkMin(f[x][i][u][v], f[x][i - 1][u][k] + f[fa[x][i - 1]][i - 1][k][v]); } } for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat) continue; dfs(y, x, depth + 1); } } inline void solve(int x, int tx, int y, int ty) { if(dep[x] < dep[y]) swap(x, y), swap(tx, ty); ll resx[2] = {inf, inf}, resy[2] = {inf, inf}, tox[2], toy[2]; resx[tx] = h[x][tx], resy[ty] = h[y][ty]; for(int i = 18; i >= 0; i--) if(dep[fa[x][i]] >= dep[y]) { tox[0] = tox[1] = inf; for(int u = 0; u < 2; u++) for(int v = 0; v < 2; v++) chkMin(tox[u], resx[v] + f[x][i][v][u]); resx[0] = tox[0], resx[1] = tox[1]; x = fa[x][i]; } if(x == y) { printf("%lld\n", resx[ty] + g[y][ty]); return; } for(int i = 18; i >= 0; i--) if(fa[x][i] != fa[y][i]) { tox[0] = tox[1] = inf; for(int u = 0; u < 2; u++) for(int v = 0; v < 2; v++) chkMin(tox[u], resx[v] + f[x][i][v][u]); resx[0] = tox[0], resx[1] = tox[1]; toy[0] = toy[1] = inf; for(int u = 0; u < 2; u++) for(int v = 0; v < 2; v++) chkMin(toy[u], resy[v] + f[y][i][v][u]); resy[0] = toy[0], resy[1] = toy[1]; x = fa[x][i], y = fa[y][i]; } int z = fa[x][0]; ll res = h[z][0] - h[x][1] - h[y][1] + g[z][0] + resx[1] + resy[1]; chkMin(res, h[z][1] - min(h[x][0], h[x][1]) - min(h[y][0], h[y][1]) + g[z][1] + min(resx[0], resx[1]) + min(resy[0], resy[1])); printf("%lld\n", res); } int main() { read(n), read(qn); char typ[5]; scanf("%s", typ); for(int i = 1; i <= n; i++) read(a[i]); for(int x, y, i = 1; i < n; i++) { read(x), read(y); add(x, y), add(y, x); ex[pin(x, y)] = ex[pin(y, x)] = 1; } dp1(1, 0), dp2(1, 0), dfs(1, 0, 1); for(int x, tx, y, ty; qn--; ) { read(x), read(tx), read(y), read(ty); if(ex.find(pin(x, y)) != ex.end() && !tx && !ty) puts("-1"); else solve(x, tx, y, ty); } return 0; }