[UOJ 207]共价大爷游长沙
给你一棵n个点的树,要求你滋磁以下操作(共m次):
- 对于给定的点对(x,y)和(u,v),删除边(x,y),添加边(u,v)。保证操作时(u,v)存在,并且保证操作后的图还是一棵树。
- 向集合S中加入一个点对(u,v)。
- 从S中删除一个点对。
- 给定一条边(x,y)(保证在当前树中存在),求问是否所有(u,v)∈S都满足u到v的路径经过了(x,y)。
1≤n≤105,1≤m≤300000。
[UOJ 14][UER #1]DZY Loves Graph
一张n个点的图,初始没边,然后要求你支持以下操作:
- 给定(x,y),在x和y两点间添加一条长度为i的边(假设这是第i次操作)。
- 给定k,删除当前图中边权最大的k条边。
- 撤销上一次操作。保证存在上一次操作且不是撤销操作。
共m次操作,每次操作后输出当前图最小生成树的边权和(不存在的话输出0)。
1≤n≤300000,1≤m≤500000。
[UOJ 50][UR #3]链式反应
给你一个集合S(保证其中元素都为小于n的自然数),定义一棵合法的树为一棵编号满足堆的性质,且非叶子节点都一定有两个可能非叶子的儿子,同时有c(c∈S)个一定为叶子的儿子。对于所有i=1…n,求有多少大小为i的形态不同的合法的树。
n≤200000。
[UOJ 333][NOIP2017]宝藏
啊啊啊爽到力,,,
过去太池沼力,没有做出来惹。现在看了看可能比较简单罢……
定义状态di,S表示当前已经被覆盖的点集为S,然后当前的生成树已经考虑到了第i层,转移看起来很毒……
所以预处理一个点集到另一个点集只走一步边来扩展,最优解是多少,然后转移就方便力……
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | #include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> const int maxn = 12; int G[15][15]; const int INF = 0x3f3f3f3f; void add_edge( int u, int v, int d) { G[u][v] = std::min(G[u][v], d); G[v][u] = std::min(G[v][u], d); } int n; int g1[(1 << maxn)][15], g2[(1 << maxn)][(1 << maxn)]; void process() { for ( int s = 1; s < (1 << n); s ++) { for ( int i = 1; i <= n; i ++) { g1[s][i] = INF; for ( int j = 0; j < n; j ++) { if ((1 << j) & s) { g1[s][i] = std::min(g1[s][i], G[j + 1][i]); } } } } for ( int s = 1; s < (1 << n); s ++) { int k = (1 << n) - 1; k = k & (~s); for ( int t = k; t > 0; t = (t - 1) & k) { g2[s][t] = 0; for ( int i = 1; i <= n; i ++) { if ((1 << (i - 1)) & t) { if (g1[s][i] == INF) { g2[s][t] = INF; break ; } g2[s][t] += g1[s][i]; } } } } } using ll = long long ; ll d[14][(1 << maxn)]; ll dp() { for ( int s = 0; s < (1 << n); s ++) { d[1][s] = INF; } for ( int i = 0; i < n; i ++) { d[1][(1 << i)] = 0; } for ( int i = 2; i <= n; i ++) { for ( int s = 1; s < (1 << n); s ++) { d[i][s] = INF; for ( int t = (s - 1) & s; t != 0; t = (t - 1) & s) { d[i][s] = std::min(d[i][s], d[i - 1][t] + (ll(i - 1)) * g2[t][s ^ t]); } } } ll ans = INF; for ( int i = 1; i <= n; i ++) { ans = std::min(ans, d[i][(1 << n) - 1]); } return ans; } int main() { memset (G, 0x3f, sizeof (G)); int m; scanf ( "%d%d" , &n, &m); for ( int i = 1; i <= m; i ++) { int u, v, w; scanf ( "%d%d%d" , &u, &v, &w); add_edge(u, v, w); } process(); printf ( "%lld\n" , dp()); return 0; } |
[UOJ 195][ZJOI2016]大♂森林
ETT野蛮,LCT文明,,,
换生长结点这个操作非常的麻烦。所以考虑给每个生长操作建立一个虚点,每个实点(就是在树中真实存在的点)向他之前最晚建立的一个虚点认父。
然后虚点初始的时候应该向上一次的那个虚点认父(我们可以近似的认为第一个虚点就是1)。然后我们用类似于扫描线的做法,等到了虚点存在的区间里就把它爸爸改成相应的实点,出去了相应区间之后就再改回来。这样这题就很好做了,我们认为虚点点权为0,实点点权为1,然后查询就好做了。
然后还有一点细节问题……比如说换生长结点的时候如何处理x在某一棵树里不存在的情况。但这个不难处理,因为同一个编号的结点一定分布编号连续的一段树里,所以真实起作用的操作范围可以认定为数据给出的操作范围和x的分布区间的交。
再有一点就是查询的时候……如果直接查询两点间splay的和的话,可能会忽略掉一些本来在原图上该有的实点。所以我们要求两点到根的距离,再用LCA去掉不需要的。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | #include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <vector> #include <utility> const int maxn = 100005; const int maxm = 200005; const int maxs = maxm + maxm; struct Node { Node *fa, *ch[2]; int val, sumv; bool rev; int d() { return (( this == fa -> ch[1]) ? 1 : 0); } void sc(Node *c, int dir) { ch[dir] = c; c -> fa = this ; } void maintain() { sumv = ch[0] -> sumv + ch[1] -> sumv + val; } void paint() { rev = !rev; std::swap(ch[0], ch[1]); } void pushdown() { if (rev) { ch[0] -> paint(); ch[1] -> paint(); rev = false ; } } }; Node pool[maxs]; Node *nil, *cur; void init_pool() { nil = cur = pool; nil -> val = nil -> sumv = 0; nil -> rev = false ; nil -> fa = nil -> ch[0] = nil -> ch[1] = nil; } #define T(x) (pool + (x)) Node *refresh(Node *x, int val = 0) { x -> val = x -> sumv = val; x -> rev = false ; x -> fa = x -> ch[0] = x -> ch[1] = nil; return x; } bool is_root(Node *x) { return (x -> fa == nil || (x -> fa -> ch[0] != x && x -> fa -> ch[1] != x)); } void zig(Node *x) { Node *y = x -> fa; int d = x -> d(); if (is_root(y)) { x -> fa = y -> fa; } else { y -> fa -> sc(x, y -> d()); } y -> sc(x -> ch[1 ^ d], d); x -> sc(y, 1 ^ d); y -> maintain(); x -> maintain(); } void splay(Node *x) { while (!is_root(x)) { Node *y = x -> fa; if (!is_root(y)) y -> fa -> pushdown(); y -> pushdown(); x -> pushdown(); if (!is_root(y)) { if ((x -> d()) ^ (y -> d())) { zig(x); } else { zig(y); } } zig(x); } // x -> maintain(); } Node *access(Node *x) { Node *nx = x, *y = nil; Node *ct = T(1); while (x != nil) { splay(x); x -> pushdown(); if (x -> fa == nil) ct = x; x -> sc(y, 1); x -> maintain(); y = x; x = x -> fa; } splay(nx); return ct; } Node *evert(Node *x) { access(x); x -> paint(); return x; } void link(Node *x, Node *y) { evert(x); x -> fa = y; } void cut(Node *x) { access(x); x -> ch[0] -> fa = nil; x -> ch[0] = nil; x -> maintain(); } void cut(Node *x, Node *y) { evert(x); access(y); int d = x -> d(); y -> ch[d] = nil; y -> maintain(); x -> fa = nil; } int ans[maxm]; using pii = std::pair< int , int >; int n; pii seg_and( int a, int b, int x, int y) { if (a > x) { std::swap(a, x), std::swap(b, y); } if (b < x) return std::make_pair(n + 1, n + 1); if (b >= y) return std::make_pair(x, y); return std::make_pair(x, b); } int ope[maxm][4]; int seg[maxm][2]; Node *bef[maxm]; std::vector< int > beg[maxn], end[maxn]; std::vector< int > query[maxn]; // #define OUTP // #define LOCAL int main() { #ifdef OUTP freopen ( "forest1.in" , "r" , stdin); freopen ( "out" , "w+" , stdout); #endif int m; scanf ( "%d%d" , &n, &m); init_pool(); refresh(T(1), 1); int cnt0 = 1, cnt1 = 0, cnt2 = 0; Node *last1 = T(1); seg[1][0] = 1; seg[1][1] = n; for ( int i = 1; i <= m; i ++) { scanf ( "%d%d%d" , &ope[i][0], &ope[i][1], &ope[i][2]); if (ope[i][0]) { scanf ( "%d" , &ope[i][3]); } if (ope[i][0] == 0) { int c = ++ cnt0; refresh(T(c), 1); link(last1, T(c)); seg[c][0] = ope[i][1]; seg[c][1] = ope[i][2]; #ifdef LOCAL printf ( "Node %d : (%d, %d)\n" , c, seg[c][0], seg[c][1]); #endif } else if (ope[i][0] == 1) { Node *n1 = T(m + i); refresh(n1); link(n1, bef[i] = last1); int l = ope[i][1], r = ope[i][2], x = ope[i][3]; auto s = seg_and(l, r, seg[x][0], seg[x][1]); l = s.first, r = s.second; #ifdef LOCAL printf ( "Change : (%d, %d) -> %d\n" , l, r, x); #endif beg[l].push_back(i); end[r + 1].push_back(i); last1 = n1; } else { int x = ope[i][1], u = ope[i][2], v = ope[i][3]; query[x].push_back(i); } } for ( int i = 1; i <= n; i ++) { for (auto id : end[i]) { Node *n1 = T(m + id); cut(n1, T(ope[id][3])); link(n1, bef[id]); } for (auto id : beg[i]) { Node *n1 = T(m + id); cut(n1, bef[id]); link(n1, T(ope[id][3])); } for (auto id : query[i]) { int u = ope[id][2], v = ope[id][3]; evert(T(1)); access(T(u)); int ret = T(u) -> sumv; Node *lca = access(T(v)); ret += T(v) -> sumv; access(lca); ret -= lca -> sumv; ret -= lca -> sumv - 1; ans[id] = ret - 1; } } for ( int i = 1; i <= m; i ++) { if (ope[i][0] == 2) { printf ( "%d\n" , ans[i]); } } return 0; } |
[UOJ 113][UER #2]手机的生产
神哎……
既然与的优先级比或高,那么我们就可以用或把程序切成若干段,每一段的值决定了最后的取值。
考虑从左往右计算某一段。如果有一个手机某一段运算结果为0,那么它会继续往下运算,反之则会停止运算。
对于每一个在前i段的值都是0,然后开始跑第i+1段的手机。他们都会额外产生l(这一段的fork个数)个手机,并且由于这些手机一被生产出来的返回值就是0,所以新的这些手机在这一段取值为0。而原来的老手机,在这一段的取值事1,于是之后的运算就不会接着做了。
这样一来,我们就可以动态维护在之前的段里的返回值全是0的手机,然后就可以做了。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <vector> using ll = long long ; const int maxn = 100005; const ll ha = 998244353LL; bool op[maxn]; int main() { std::vector< int > vec; int last = 0; int n; scanf ( "%d" , &n); for ( int i = 1; i <= (n - 1); i ++) { char buf[5]; scanf ( "%s" , buf); if (buf[0] == '|' ) { vec.push_back(i - last); last = i; } } vec.push_back(n - last); ll pre0 = 1LL; ll ans = 1LL; for (auto x : vec) { ans += (pre0 * (ll(x))) % ha; ans %= ha; pre0 = (pre0 * (ll(x))) % ha; } printf ( "%lld\n" , ans); return 0; } |
[UOJ 48][UR #3]核聚变反应堆
先去考虑两个变量的sgcd怎么求……我们先求出他们的gcd,然后在除掉一个最小的质因子即可。
那么我们先把a1分解质因数,然后所有数和它求一个gcd,然后去去掉一个尽可能小的质因子。注意到一个数p的质因子数量是O(logp)级别的,所以每一个数找到要除的那个最小的质因子只需要O(loga1)的时间。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <vector> #include <cmath> const int maxn = 100005; using ll = long long ; ll V[maxn]; int vcnt = 0; void des(ll x) { ll m = (ll) sqrt (x + 0.5); for (ll i = 2; i <= m; i ++) { if (x % i == 0) { V[vcnt ++] = i; while (x % i == 0) x /= i; } } if (x > 1LL) V[vcnt ++] = x; } ll gcd(ll a, ll b) { if (!b) return a; else return gcd(b, a % b); } ll A[maxn]; int main() { int n; scanf ( "%d" , &n); for ( int i = 1; i <= n; i ++) { scanf ( "%lld" , &A[i]); } des(A[1]); for ( int i = 1; i <= n; i ++) { if (i > 1) putchar ( ' ' ); ll bs = gcd(A[i], A[1]); if (bs == 1) { printf ( "-1" ); continue ; } for ( int j = 0; j < vcnt; j ++) { if (A[i] % V[j] == 0) { bs /= V[j]; break ; } } printf ( "%lld" , bs); } puts ( "" ); return 0; } |
[UOJ 21][UR #1]缩进优化
神题啊……
考虑让答案最小不好做,那么我们想,把连续的一段空格变成一个TAB(假设TAB长度为x)就是减少x−1空格,那么我们尝试去最大化减小的空格的量。
然后考虑枚举x是啥,然后去算减少的空格的量。对于任意ai,减少的空格量就是(x−1)⌊aix⌋,这似乎可以整除分块哎……
但是会被T掉。我们想,其实我们对任意x,我们去枚举⌊aix⌋就行啦,要查询满足条件的ai数量可以预处理一个权值前缀和然后就能O(1)做啦、
假设X=max,那么每一个x对时间复杂度的贡献就是O(\frac{X}{x})。这不就是调和级数吗?所以时间复杂度事O(X\ln X)。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | #include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> const int maxn = 1000005; typedef long long ll; int a[maxn], C[maxn]; int n, bd; void process() { for ( int i = 1; i <= n; i ++) { C[a[i]] ++; } for ( int i = 1; i <= bd; i ++) { C[i] += C[i - 1]; } } ll sum; ll query( int x, int p) { int l = x * p; int r = x * (p + 1) - 1; if (r > bd) r = bd; return (C[r] - C[l - 1]); } ll calc( int x) { ll delta = 0; for ( int i = 1; i <= (bd / x); i ++) { ll cnt = query(x, i); delta += (ll(i)) * cnt * (ll(x - 1)); } #ifdef LOCAL printf ( "ans of %d : %lld\n" , x, sum - delta); #endif return (sum - delta); } int main() { scanf ( "%d" , &n); sum = 0LL; bd = 0; for ( int i = 1; i <= n; i ++) { scanf ( "%d" , &a[i]); bd = std::max(bd, a[i]); sum += a[i]; } process(); ll ans = sum; for ( int i = 1; i <= bd; i ++) { ans = std::min(ans, calc(i)); } printf ( "%lld\n" , ans); return 0; } |
[UOJ 62][UR #5]怎样跑得更快
一年前看的反演神题……(然后现在才A)
[UOJ 110][APIO2015]Bali Sculptures
第一次做subtask题= =其实这题也没啥难度吧
其实就是两个subtask……
对于第一个subtask,就是满足1\leq n\leq 100且1\leq a\leq b\leq n。我们应该优先满足高位为0,于是乎可以贪心,从高位枚举到低位,看是否能在满足之前几位的限制条件的同时满足这一位答案为0。这个判定过程的话,可以设计状态d[i][k]表示前i位割成k段是否满足约束条件,枚举断点O(n)转移。
第二个subtask,虽然有n\leq 2000但是满足a = 1。这意味着分段数想怎么小就怎么小,所以我们可以直接去求这个序列要满足限制条件的话最少可以割成几段,这样的话第二维就可以省掉了。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | #include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> typedef long long ll; const int maxn = 2005; ll Y[maxn], S[maxn]; int n, a, b; const int maxb = 41; ll st = 0; namespace Task1 { bool d[105][105]; bool dp( int bi) { memset (d, 0, sizeof (d)); d[0][0] = true ; for ( int i = 1; i <= n; i ++) { for ( int k = 1; k <= b; k ++) { for ( int j = 0; j < i; j ++) { ll sum = S[i] - S[j]; if ((sum & (st | (1LL << bi))) == 0LL) { d[i][k] = (d[i][k] || d[j][k - 1]); } } } } for ( int k = a; k <= b; k ++) { if (d[n][k]) return true ; } return false ; } }; namespace Task2 { int d[maxn]; bool dp( int bi) { d[0] = 0; for ( int i = 1; i <= n; i ++) { d[i] = 0x3f3f3f3f; for ( int j = 0; j < i; j ++) { ll sum = S[i] - S[j]; if (!(sum & (st | (1LL << bi)))) { d[i] = std::min(d[i], d[j] + 1); } } } return (d[n] <= b); } }; ll solve() { ll ans = 0; for ( int i = maxb; i >= 0; i --) { bool flag = n <= 100 ? Task1::dp(i) : Task2::dp(i); if (flag) { st |= (1LL << i); } else { ans |= (1LL << i); } } return ans; } int main() { scanf ( "%d%d%d" , &n, &a, &b); for ( int i = 1; i <= n; i ++) { scanf ( "%lld" , &Y[i]); S[i] = S[i - 1] + Y[i]; } printf ( "%lld\n" , solve()); return 0; } |