Z natizen gala ovasug narrin qhe ba zbik
Quatorm arrticive nafjenvt uane hak cfnik
Wafhte unitalize napivet uavidnafk fhatox
Orz pyz ak ioi
(火星文)
——《Ydjadf fha de NOIP 2018》
Day2 T1 旅行 travel
算法:模拟
- 一棵树的情况,直接从 111 开始 DFS ,为了保证字典序最小,递归时需要按标号从小到大枚举子节点
- 复杂度 O(n)O(n)O(n)
- 基环树的情况,枚举环上一条边删掉,再执行上述过程
- 复杂度 O(n2)O(n^2)O(n2)
- 注意子节点的编号大小顺序需要预处理
- 否则复杂度多个 log\loglog
代码
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 5005, M = N << 1;
int n, m, ecnt, nxt[M], adj[N], st[M], go[M], a[N], T, ans[N], lins[N][N], X, Y;
bool vis[N], adiao;
void add_edge(int u, int v)
{
nxt[++ecnt] = adj[u]; adj[u] = ecnt; st[ecnt] = u; go[ecnt] = v;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; st[ecnt] = v; go[ecnt] = u;
}
void dfs(int u, int fu)
{
int i;
a[++T] = u;
For (i, 1, lins[u][0])
{
if (lins[u][i] == fu) continue;
dfs(lins[u][i], u);
}
}
void dfs2(int u, int fu)
{
int i;
a[++T] = u;
vis[u] = 1;
For (i, 1, lins[u][0])
{
if (lins[u][i] == fu || (u == X && lins[u][i] == Y)
|| (u == Y && lins[u][i] == X)) continue;
if (!vis[lins[u][i]]) dfs2(lins[u][i], u);
else adiao = 1;
}
}
bool compares()
{
int i;
For (i, 1, n)
{
if (a[i] < ans[i]) return 1;
if (a[i] > ans[i]) return 0;
}
return 0;
}
int main()
{
int i, j, x, y;
n = read(); m = read();
For (i, 1, m) x = read(), y = read(),
add_edge(x, y);
For (i, 1, n) Edge(i) lins[i][++lins[i][0]] = v;
For (i, 1, n) std::sort(lins[i] + 1, lins[i] + lins[i][0] + 1);
if (m == n - 1)
{
dfs(1, 0);
For (i, 1, n) printf("%d ", a[i]);
std::cout << std::endl;
return 0;
}
For (i, 1, n) ans[i] = n + 1;
For (i, 1, m)
{
T = 0; adiao = 0;
X = st[i << 1], Y = go[i << 1];
For (j, 1, n) vis[j] = 0;
dfs2(1, 0);
if (!adiao && T == n && compares())
{
For (j, 1, n) ans[j] = a[j];
}
}
For (i, 1, n) printf("%d ", ans[i]);
std::cout << std::endl;
return 0;
}
Day2 T2 填数游戏 game
算法:状压 DP + 找规律
- 先思考一下什么样的 010101 矩阵满足条件
- 先把矩阵画成对角线的形式,如下图
- 上图中同色的方块在一个对角线上
- 瞎 jb 推一波,可以得出一个合法的 010101 矩阵需要同时满足两个条件:
- (1)同一个对角线要么全 000 ,要么全 111 ,要么下面一段全 111 而上面一段全 000
- (2)对于任意一个 1<i≤n,1≤j<m1<i\le n,1\le j<m1<i≤n,1≤j<m ,如果 (i,j)(i,j)(i,j) 和 (i−1,j+1)(i-1,j+1)(i−1,j+1) 填的数相同,那么 (i,j+1)(i,j+1)(i,j+1) 及右下角的所有方块中,一条斜线上的所有数需要全相等,如图
- 证明由读者自行撕烤
80pts
- 我们有了一个 808080 分的状压 DP
- f[i][j][S]f[i][j][S]f[i][j][S] 表示从下(右)往左(上)前 iii 个对角线,第 iii 个对角线放了 jjj 个 111 (由第一点性质得到, jjj 唯一确定了第 iii 个对角线的方案), SSS 是一个集合,如果第 iii 个对角线第 kkk 个格子及右下角的子矩阵每个对角线都满足其中填的数全相等则 SSS 中包含 kkk ,否则 SSS 不包含 kkk
- 大力枚举 kkk ,进行转移 f[i−1][j][S]→f[i][k][T]f[i-1][j][S]\rightarrow f[i][k][T]f[i−1][j][S]→f[i][k][T]
- TTT 可以由 j,S,kj,S,kj,S,k 计算出
- 注意需要判断第二点性质是否满足
- 使用滚动数组优化空间
- 理论复杂度 O(2nmn3)O(2^nmn^3)O(2nmn3) (计算 TTT 需要 O(n)O(n)O(n) 的时间)
- 但实际上很多转移都不满足第二点性质,可以通过 80%80\%80% 的数据
100pts
- 如果你有兴趣去找规律,设 F(n,m)F(n,m)F(n,m) 表示 n×mn\times mn×m 的矩阵的结果
- 你会发现当 n+2≤mn+2\le mn+2≤m 时, F(n,m)=3×F(n,m−1)F(n,m)=3\times F(n,m-1)F(n,m)=3×F(n,m−1)
- 于是当 n+2>mn+2>mn+2>m 时仍然状压 DP
- 否则算出 n×(n+1)n\times(n+1)n×(n+1) 的矩阵的结果
- 最后乘上 3m−n−13^{m-n-1}3m−n−1
- 复杂度 O(2nn4+logm)O(2^nn^4+\log m)O(2nn4+logm)
不知道为什么出题人不把 m 加强到 10^9
代码
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}
const int N = 40, M = (1 << 8) + 5, ZZQ = 1e9 + 7;
int n, m, AAm, Cm, len, ls[N], f[2][N][M];
int qpow(int a, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = 1ll * res * a % ZZQ;
a = 1ll * a * a % ZZQ;
b >>= 1;
}
return res;
}
int o(int S, int x)
{
return (S >> x - 1) & 1;
}
int le(int x, int y)
{
if (len - x + 1 >= n) return y - 1;
return y;
}
int ri(int x, int y)
{
if (len - x + 1 >= n) return y;
return y + 1;
}
int main()
{
int i, j, k, h, r;
std::cin >> n >> m;
if (n == 1) return printf("%d\n", qpow(2, m)), 0;
AAm = m;
if (m > n + 1) m = n + 1;
len = n + m - 1;
Cm = (1 << n) - 1;
f[1][1][1] = f[1][0][1] = 1;
For (i, 1, n) ls[len - i + 1] = Min(i, m);
For (i, 2, m) ls[len - (i + n - 1) + 1] = Min(n, m - i + 1);
For (i, 2, len)
{
int op = i & 1;
For (j, 0, n) For (k, 0, Cm) f[op][j][k] = 0;
int Cn = (1 << ls[i - 1]) - 1;
For (j, 0, ls[i - 1]) For (k, 0, Cn)
{
if (!f[op ^ 1][j][k]) continue;
For (h, 0, ls[i])
{
bool is = 1;
For (r, 1, ls[i] - 1)
if (r != h && !o(k, ri(i, r)))
{is = 0; break;}
if (is)
{
int S = 0;
For (r, 1, ls[i])
{
int kl = le(i, r), kr = ri(i, r);
if (kl < 1 || kl > ls[i - 1])
{
if (o(k, kr)) S |= 1 << r - 1;
}
else if (kr < 1 || kr > ls[i - 1])
{
if (o(k, kl)) S |= 1 << r - 1;
}
else if (o(k, kl) && o(k, kr)
&& j != kl) S |= 1 << r - 1;
}
f[op][h][S] = (f[op][h][S] + f[op ^ 1][j][k]) % ZZQ;
}
}
}
}
int res = ((f[len & 1][0][0]
+ f[len & 1][0][1]) % ZZQ + (f[len & 1][1][0]
+ f[len & 1][1][1]) % ZZQ) % ZZQ;
if (AAm > m) res = 1ll * res * qpow(3, AAm - m) % ZZQ;
std::cout << res << std::endl;
return 0;
}
Day2 T3 保卫王国 defense
算法:树形 DP + 树上倍增
仿佛是 NOIP 第一道 5K+ 的码农题- 如果没有询问,那么就是经典的树形 DP
- f[u][0]f[u][0]f[u][0] 表示 uuu 的子树内,不选点 uuu 并覆盖所有边的最小代价
- f[u][1]f[u][1]f[u][1] 表示 uuu 的子树内,选点 uuu 并覆盖所有边的最小代价
- f[u][0]=∑v∈son(u)f[v][1]f[u][0]=\sum_{v\in son(u)}f[v][1]f[u][0]=v∈son(u)∑f[v][1]
- f[u][1]=pu+∑v∈son(u)min(f[v][0]+f[v][1])f[u][1]=p_u+\sum_{v\in son(u)}\min(f[v][0]+f[v][1])f[u][1]=pu+v∈son(u)∑min(f[v][0]+f[v][1])
据说很多大佬都用动态 DP ,是不是就我这个傻逼写倍增- 再设 g[u][0]g[u][0]g[u][0] 和 g[u][1]g[u][1]g[u][1] 表示 uuu 的父亲的子树去掉 uuu 的子树,不选 / 选 uuu 的父亲的最小代价
- 然后再进行一次换根, h[u][0]h[u][0]h[u][0] 和 h[u][1]h[u][1]h[u][1] 表示整棵树去掉 uuu 的子树, uuu 的父亲不选 / 选的最小代价
- 预处理出倍增数组 sum[u][i][s1][s2]sum[u][i][s_1][s_2]sum[u][i][s1][s2] ,表示 uuu 到 uuu 的第 2i−12^i-12i−1 级祖先共 2i2^i2i 个点的下面的式子( s1,s2,s3,s4s_1,s_2,s_3,s_4s1,s2,s3,s4 为布尔值)
- maxopk=0/1,k∈[1,2i]op1=s1,op2=s2i(∀1≤k<2i,opk OR opk+1≠0)∑k=12ig[uk][opk]\max_{op_{k}=0/1,k\in[1,2^i]}^{op_1=s_1,op_2=s_{2^i}}(\forall1\le k<2^i,op_k\text{ OR }op_{k+1}\ne 0)\sum_{k=1}^{2^i}g[u_k][op_k]opk=0/1,k∈[1,2i]maxop1=s1,op2=s2i(∀1≤k<2i,opk OR opk+1̸=0)k=1∑2ig[uk][opk]
- 其中 uku_kuk 表示 uuu 的 k−1k-1k−1 级祖先
- sum[u][i+1][s1][s2]=maxs3∈[0,1],s4∈[0,1]s3 OR s4≠0{sum[u][i][s1][s3]+sum[fa[u][i]][s4][s2]}sum[u][i+1][s_1][s_2]=\max_{s_3\in[0,1],s_4\in[0,1]}^{s_3\text{ OR }s_4\ne 0}\{sum[u][i][s_1][s_3]+sum[fa[u][i]][s_4][s_2]\}sum[u][i+1][s1][s2]=s3∈[0,1],s4∈[0,1]maxs3 OR s4̸=0{sum[u][i][s1][s3]+sum[fa[u][i]][s4][s2]}
- 其中 fa[u][i]fa[u][i]fa[u][i] 表示 uuu 的 2i2^i2i 级祖先
- 询问时分三种情况
- (1) aaa 和 bbb 相邻且 x=y=0x=y=0x=y=0 ,输出 −1-1−1
- (2) aaa 是 bbb 的祖先(如果 bbb 是 aaa 的祖先则交换 aaa 和 bbb 并交换 xxx 和 yyy )
- ①处理 bbb 的子树:为答案贡献 f[b][y]f[b][y]f[b][y]
- ②处理 aaa 的子树之外:如果 x=1x=1x=1 则为答案贡献 min(h[a][0],h[a][1])\min(h[a][0],h[a][1])min(h[a][0],h[a][1]) 否则贡献 h[a][1]h[a][1]h[a][1]
- ③处理 aaa 的子树(不包括包含 aaa 的子树包含 bbb 的子节点的子树):如果 x=1x=1x=1 则为答案贡献 f[a][1]−min(f[d][0],f[d][1])f[a][1]-\min(f[d][0],f[d][1])f[a][1]−min(f[d][0],f[d][1]) ,否则贡献 f[a][0]−f[d][1]f[a][0]-f[d][1]f[a][0]−f[d][1]
- ddd 为 bbb 向上跳 dep[b]−dep[a]dep[b]-dep[a]dep[b]−dep[a] 后到达的点,可以用倍增找到
- ( dep[u]dep[u]dep[u] 表示 uuu 的深度)
- ④也是最重要的情况: aaa 到 bbb 的路径
- 回忆刚刚定义的 sum[u][i][0/1][0/1]sum[u][i][0/1][0/1]sum[u][i][0/1][0/1] ,我们可以使用二进制分组的思想
- 把 aaa 到 bbb 之间(不包括 aaa 和 bbb )一共 dep[b]−dep[a]−1dep[b]-dep[a]-1dep[b]−dep[a]−1 个点拆成不超过 O(logn)O(\log n)O(logn) 段
- 利用预处理出的 sum[u][i][0/1][0/1]sum[u][i][0/1][0/1]sum[u][i][0/1][0/1] 倍增合并,得到 aaa 到 bbb 的路径的贡献
- 注意需要考虑在 xxx 和 yyy 的限制下路径的首尾是否能选
- (3) aaa 和 bbb 的 LCA 不是 aaa 或 bbb
- 枚举 LCA 选或不选,拆成 aaa 到 LCA 和 bbb 到 LCA 两条路径进行判断
- 和(2)基本相同
- 复杂度 O(nlogn)O(n\log n)O(nlogn)
- 由于 sumsumsum 合并时有 161616 的常数,所以实际效率不高
如果这题出成省选题,怕不是会要限制多个点,然后虚树 DP
码农报告
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define Tree(u) for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu)
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
typedef long long ll;
template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}
template <class T>
inline void Swap(T &a, T &b) {a ^= b; b ^= a; a ^= b;}
const ll INF = 1ll << 61;
const int N = 1e5 + 5, M = N << 1, LogN = 20;
int n, m, ecnt, nxt[M], adj[N], go[M], p[N], dep[N], fa[N][LogN], tot, sons[N];
ll f[N][2], g[N][2], h[N][2], as[N], bs[N], sumla[N], sumra[N], sumlb[N], sumrb[N];
struct node
{
ll a[2][2];
void init()
{
a[0][0] = a[1][1] = a[0][1] = a[1][0] = INF;
}
friend inline node operator + (node a, node b)
{
int i, j, k, h;
node res; res.init();
For (i, 0, 1) For (j, 0, 1) For (k, 0, 1) For (h, 0, 1)
if (k || h) res.a[i][j] = Min(res.a[i][j], a.a[i][k] + b.a[h][j]);
return res;
}
} sum[N][LogN];
void add_edge(int u, int v)
{
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
char s[22];
void dfs(int u, int fu)
{
dep[u] = dep[fu] + 1;
f[u][0] = 0; f[u][1] = p[u];
fa[u][0] = fu;
Tree(u)
{
dfs(v, u);
f[u][0] += f[v][1];
f[u][1] += Min(f[v][0], f[v][1]);
}
Tree(u)
{
g[v][0] = f[u][0] - f[v][1];
g[v][1] = f[u][1] - Min(f[v][0], f[v][1]);
}
}
void dfs2(int u, int fu)
{
int i;
tot = 0;
Tree(u)
{
as[++tot] = f[v][1];
bs[tot] = Min(f[v][0], f[v][1]);
sons[tot] = v;
}
sumla[0] = sumra[tot + 1] = sumlb[0] = sumrb[tot + 1] = 0;
For (i, 1, tot)
{
sumla[i] = sumla[i - 1] + as[i];
sumlb[i] = sumlb[i - 1] + bs[i];
}
Rof (i, tot, 1)
{
sumra[i] = sumra[i + 1] + as[i];
sumrb[i] = sumrb[i + 1] + bs[i];
}
For (i, 1, tot)
{
int v = sons[i];
h[v][0] = sumla[i - 1] + sumra[i + 1] + h[u][1];
h[v][1] = sumlb[i - 1] + sumrb[i + 1] + Min(h[u][0], h[u][1]) + p[u];
}
Tree(u) dfs2(v, u);
}
void bz(int u, int fu)
{
int i, j, h, k, w;
sum[u][0].a[0][0] = g[u][0];
sum[u][0].a[0][1] = sum[u][0].a[1][0] = INF;
sum[u][0].a[1][1] = g[u][1];
For (i, 0, 17)
{
fa[u][i + 1] = fa[fa[u][i]][i];
sum[u][i + 1] = sum[u][i] + sum[fa[u][i]][i];
}
Tree(u) bz(v, u);
}
int lca(int u, int v)
{
if (dep[u] < dep[v]) Swap(u, v);
int i;
Rof (i, 18, 0)
{
if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
if (u == v) return u;
}
Rof (i, 18, 0)
if (fa[u][i] != fa[v][i])
u = fa[u][i], v = fa[v][i];
return fa[u][0];
}
int jmp(int u, int dis)
{
int i;
Rof (i, 18, 0)
if ((dis >> i) & 1) u = fa[u][i];
return u;
}
node anc(int u, int dis)
{
int i, tmp = -1;
node res;
dis++;
Rof (i, 18, 0)
if ((dis >> i) & 1)
{
if (tmp == -1) tmp = 0, res = sum[u][i];
else res = res + sum[u][i];
u = fa[u][i];
}
return res;
}
int main()
{
int i, j, k, a, b, x, y;
n = read(); m = read();
scanf("%s", s + 1);
For (i, 1, n) p[i] = read();
For (i, 1, n - 1) x = read(), y = read(), add_edge(x, y);
dfs(1, 0); dfs2(1, 0); bz(1, 0);
while (m--)
{
a = read(); x = read(); b = read(); y = read();
if ((fa[a][0] == b || fa[b][0] == a) && !x && !y)
{
puts("-1");
continue;
}
int c = lca(a, b);
if (c == a || c == b)
{
if (c == b) Swap(a, b), Swap(x, y);
ll ans = x ? Min(h[a][0], h[a][1]) : h[a][1];
ans += f[b][y];
if (fa[a][0] != b && fa[b][0] != a)
{
node tmp = anc(b, dep[b] - dep[a] - 2);
ll delta = INF;
For (i, 0, 1)
{
if (!y && !i) continue;
For (j, 0, 1)
{
if (!x && !j) continue;
delta = Min(delta, tmp.a[i][j]);
}
}
ans += delta;
}
int d = jmp(b, dep[b] - dep[a] - 1);
if (x) ans += f[a][1] - Min(f[d][0], f[d][1]);
else ans += f[a][0] - f[d][1];
printf("%lld\n", ans);
}
else
{
ll ans = INF;
For (i, 0, 1)
{
ll res = f[a][x] + f[b][y]
+ (i ? Min(h[c][0], h[c][1]) : h[c][1]);
if ((fa[a][0] == c && !x && !i) ||
(fa[b][0] == c && !y && !i)) continue;
if (fa[a][0] != c)
{
node tmp = anc(a, dep[a] - dep[c] - 2);
ll delta = INF;
For (j, 0, 1)
{
if (!x && !j) continue;
For (k, 0, 1)
{
if (!i && !k) continue;
delta = Min(delta, tmp.a[j][k]);
}
}
res += delta;
}
if (fa[b][0] != c)
{
node tmp = anc(b, dep[b] - dep[c] - 2);
ll delta = INF;
For (j, 0, 1)
{
if (!y && !j) continue;
For (k, 0, 1)
{
if (!i && !k) continue;
delta = Min(delta, tmp.a[j][k]);
}
}
res += delta;
}
int d = jmp(a, dep[a] - dep[c] - 1),
e = jmp(b, dep[b] - dep[c] - 1);
if (i) res += f[c][1] - Min(f[d][0], f[d][1]) - Min(f[e][0], f[e][1]);
else res += f[c][0] - f[d][1] - f[e][1];
ans = Min(ans, res);
}
printf("%lld\n", ans);
}
}
return 0;
}