The 2nd Universal Cup Stage 1: Qingdao, Sep 2-3, 2023 https://contest.ucup.ac/contest/1339
A. 有 $m$ 个 perfect 和 $n-m$ 个 nonperfect,分别使得连续段最大或者最小。
连续段最大显然是 $m$ 个 perfect 连在一起。
连续段最小的话可以考虑将把 $m$ 拆成 $n-m+1$ 段($n-m$ 个可以拆分成 $n-m+1$ 段)
所以答案是 $m$ 和 $\lceil \frac{m}{n-m+1}\rceil$。
B. 每个点的代价是离他最近的一个红点祖先的距离,每次询问选出 $k$ 个点,你可以随意改变一个点的颜色,使得 $\max{ cost_{v_i} }$ 最小。
要想要最大值变小,先将 $cost_{v_i}$ 排序,然后可以知道选的点一定在 $lca(v_i….v_k)$ 上。
所以每次的答案就是 $\max{cost{v_{i-1}}, \max{dep_{v_i} … dep_{v_k}} - dep_{lca}}$。
时间复杂度 $O(n \log n)$。
C. 总状态数是 $256n$,如果模拟了 $256n$ 步没有走出去,说明一定走了重复的路,也就是走不出去。
D. 从上往下扫描线。
线段树跑了 1.5s,在 pta 上过了(时限 2s)。
用 set 倒是过得很容易。
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 #include <bits/stdc++.h> #ifndef LOCAL #define debug(...) #else #define debug(...) cerr << "[" << #__VA_ARGS__ << "]:" , debug_out(__VA_ARGS__) #endif #define rep(i, x, y) for (int i = x; i < y; i++) #define rp(i, n) rep(i, 0, n) #define pb emplace_back using namespace std;template <typename T, typename T2> void cmin (T &x, const T2 &y) { x = x < y ? x : y; } template <typename T, typename T2> void cmax (T &x, const T2 &y) { x = x > y ? x : y; } using ll = long long ;using vi = vector<int >;using pii = pair<int , int >;template <class T > using vc = vector<T>;template <class T > using pq = priority_queue<T>;template <class T > using pqg = priority_queue<T, vector<T>, greater<T>>;mt19937 rng (time(NULL )) ;const int inf = 1000000000 ;const ll lnf = 1000000000000000000 ;#define sz(x) int((x).size()) #define all(x) begin(x), end(x) void solve () { int n, m, k; cin >> n >> m >> k; vi r1 (k) , r2 (k) , c1 (k) , c2 (k) ; vc<vi> add (n) , del (n) ; rp (i, k) { cin >> r1[i] >> c1[i] >> r2[i] >> c2[i]; r1[i]--; c1[i]--; add[r1[i]].pb (i); if (r2[i] < n) { del[r2[i]].pb (i); } } vi f (k) ; iota (all (f), 0 ); auto find = [&](int x) { while (x != f[x]) { x = f[x] = f[f[x]]; } return x; }; ll ans = 0 ; int comp = 0 ; int width = 0 ; auto merge = [&](int x, int y) { x = find (x); y = find (y); if (x != y) { f[y] = x; comp--; } }; set<array<int , 3>> s; for (int x = 0 ; x < n; x++) { for (int i : add[x]) { auto it = s.lower_bound ({c1[i], 0 , 0 }); if (it != s.begin ()) { it--; } while (it != s.end () && (*it)[0 ] < c2[i]) { if ((*it)[1 ] > c1[i]) { merge ((*it)[2 ], i); } it++; } } for (int i : del[x]) { width -= c2[i] - c1[i]; s.erase ({c1[i], c2[i], i}); } for (int i : add[x]) { auto it = s.insert ({c1[i], c2[i], i}).first; if (it != s.begin ()) { auto prv = prev (it); if ((*prv)[1 ] == c1[i]) { merge ((*prv)[2 ], i); } } auto nxt = next (it); if (nxt != s.end ()) { if ((*nxt)[0 ] == c2[i]) { merge ((*nxt)[2 ], i); } } width += c2[i] - c1[i]; comp++; } ans += width; cout << ans << " " << comp << "\n" ; } } int main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); int t = 1 ; cin >> t; while (t--) { solve (); } return 0 ; }
E. https://www.luogu.com.cn/blog/jiangly/the-2nd-universal-cup-stage-1-qingdao
F. 题目里所说的划分,$V = A + B$,$A$ 是团,$B$ 是最大独立集。
首先先找到一个合法的 $|A|$ 最大的团:将度数从大到小排序,贪心的加入 $A$,如果加入之后还是团那么就加入,就可以得到一个合法的答案,然后来计算数量,注意到其他的最大团只能是 $A$ 替换掉一个点,如果可以替换两个点,就说明新加的两个点之间有边,但他们本身在 $B$ 中,矛盾。
独立集的部分取补图之后是完全一致的。
当然也可以在原图上操作。
首先如果 $A$ 中有的点只有 $|A|-1$ 条边,说明他是可以塞到独立集里面的,最多删掉一个(因为团的性质)。
然后可以知道独立集最大是 $|B|$,类似的求出来就好了。
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 #include <bits/stdc++.h> #ifndef LOCAL #define debug(...) #else #define debug(...) cerr << "[" << #__VA_ARGS__ << "]:" , debug_out(__VA_ARGS__) #endif #define rep(i, x, y) for (int i = x; i < y; i++) #define rp(i, n) rep(i, 0, n) #define pb emplace_back using namespace std;template <typename T, typename T2> void cmin (T &x, const T2 &y) { x = x < y ? x : y; } template <typename T, typename T2> void cmax (T &x, const T2 &y) { x = x > y ? x : y; } using ll = long long ;using vi = vector<int >;using pii = pair<int , int >;template <class T > using vc = vector<T>;template <class T > using pq = priority_queue<T>;template <class T > using pqg = priority_queue<T, vector<T>, greater<T>>;mt19937 rng (time(NULL )) ;const int inf = 1000000000 ;const ll lnf = 1000000000000000000 ;#define sz(x) int((x).size()) #define all(x) begin(x), end(x) void solve () { int n, m; cin >> n >> m; vc<vi> adj (n) ; rp (i, m) { int x, y; cin >> x >> y; --x, --y; adj[x].pb (y); adj[y].pb (x); } vi p (n) ; iota (all (p), 0 ); sort (all (p), [&](auto x, auto y) { return sz (adj[x]) > sz (adj[y]); }); vi vis (n) ; int cnt = 0 ; for (auto x : p) { int res = 0 ; for (auto y : adj[x]) { if (vis[y]) { res += 1 ; } } if (res == cnt) { cnt += 1 ; vis[x] = 1 ; } } int ans1 = 1 ; int ans2 = 1 ; rp (i, n) { if (!vis[i] && sz (adj[i]) == cnt - 1 ) { ans1 += 1 ; } } rp (i, n) { if (vis[i] && sz (adj[i]) == cnt - 1 ) { cnt--; vis[i] = 0 ; } } rp (i, n) { if (vis[i] && sz (adj[i]) == cnt) { ans2 += 1 ; } } cout << ans1 << " " << ans2 << "\n" ; } int main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); int t = 1 ; cin >> t; while (t--) { solve (); } return 0 ; }
G. 套个 jiangly 的做法。
就是,可以把 a 经过处理变成一个排列,不影响正确性。
具体的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 std::vector<int > a (n) ; for (int i = 0 ; i < n; i++) { std::cin >> a[i]; a[i]--; } std::vector<int > o (n) ; std::iota (o.begin (), o.end (), 0 ); std::sort (o.begin (), o.end (), [&](int i, int j) { return a[i] < a[j] || (a[i] == a[j] && i < j); }); for (int i = 0 ; i < n; i++) { a[o[i]] = i; }
然后把每一段的逆序对数都放到 multiset 里,每次删掉一个 p 之后,直接把下标在 $[l,p)$ 和 $(p,r]$ 线段树分裂出来,并打上 tag 维护每一个段的逆序对数,答案就是 multiset 里最大的元素。
H. 考虑第 i 个红绿灯的贡献即可。
I. https://blog.cubercsl.site/post/kuririn-miracle/
J. 给六个数字 a, b, c, d, t, v,给一盏灯,每隔 v+0.5 s 就会熄灭,每隔 A 秒可以拍 B 下,每隔 C 秒可以拍 D 下,如果灯是灭的,就让灯亮,如果灯是亮的,就让分数+1,并将灯重新设置成 v+0.5 s熄灭。
首先可以算出来拍了多少下灯,是 $(t/a) \times b + (t/c) \times d + b + d$,然后计算一下要拍亮几次灯就可以了。
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 void solve () { ll a, b, c, d, v, t; cin >> a >> b >> c >> d >> v >> t; vc<ll> V; ll l = lcm (a, c); V.pb (0 ); for (ll i = a; i < l; i += a) V.pb (i); for (ll i = c; i <= l; i += c) V.pb (i); sort (all (V)); ll tmp = 0 ; for (int i = 1 ; i < V.size (); i++) { if (V[i] - V[i - 1 ] > v) { tmp += 1 ; } } ll ans = (t / a) * b + (t / c) * d + b + d - 1 ; ll cur = t / l; ans = ans - cur * tmp; t %= l; for (int i = 1 ; V[i] <= t; i++) { if (V[i] - V[i - 1 ] > v) { ans -= 1 ; } } cout << ans << "\n" ; }
K. 记录最高位。