joylintp / WiwiHo
直播循環 (Cycle)
來源: Codeforces 1041C
測資: joylintp
題敘: joylintp
\(r=0\)
→ 不用休息
→ 所有遊戲都可以在第一個循環直播
→ \(k=1,x_i=1\)
3 / 100
\(n \le 10\)
→ 枚舉直播遊戲的順序
→ 盡量讓遊戲可以在同一個循環內直播
→ \(O(n! \times n)\)
9 / 100
\(n = s\)
→ 每天都有一種遊戲要玩
→ 第一個循環玩第 \(1,1+r+1,1+2r+2,\dots\) 天的遊戲
→ 第二個循環玩第 \(2,2+r+1,2+2r+2,\dots\) 天的遊戲
→ \(O(n)\)
13 / 100
每次盡量選擇還沒玩過的遊戲中 \(a_i\) 最小的
→ \(O(n)\) 迴圈尋找可玩的遊戲
→ 總複雜度 \(O(n^2)\)
18 / 100
→ \(O(n)\) 迴圈尋找可玩的遊戲
→ set
、priority_queue
\(O(\log{n})\) 維護
→ 總複雜度 \(O(n\log{n})\)
100 / 100
#include<bits/stdc++.h>
using namespace std;
#define MP make_pair
#define F first
#define S second
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int n, m, d;
cin >> n >> m >> d;
vector<pair<int, int>> a(n);
for (int i = 0; i < n; i++)
cin >> a[i].F, a[i].S = i;
sort(a.begin(), a.end());
vector<int> ans(n);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
for (int i = 0; i < n; i++)
if (pq.empty() || a[i].F - pq.top().F < d + 1)
ans[a[i].S] = pq.size() + 1, pq.push(MP(a[i].F, pq.size() + 1));
else
ans[a[i].S] = pq.top().S, pq.pop(), pq.push(MP(a[i].F, ans[a[i].S]));
cout << pq.size() << '\n';
for (int i : ans)
cout << i << ' ';
return 0;
}
兔田彩券 (Lottery)
靈感來源: JOI 2021 Final Round pC
測資: WiwiHo
題敘: joylintp
有 \(m\) 個 pair \((a_i,b_i)\)
對於每筆詢問 \((l_i,r_i,s_i,t_i)\)
求有幾個 \(j\) 滿足
\(l_i \leq a_j \leq r_i \land s_i \leq b_j \leq t_i\) 或
\(l_i \leq b_j \leq r_i \land s_i \leq a_j \leq t_i\)
\(n,m,q \leq 500\)
對於每一個詢問
把所有 pair 檢查一次是否符合條件就好了
\(O(mq)\)
\(n \leq 100\),\(q \leq 10000\)
\(n\) 很小?
pair 只有 \(O(n^2)\) 種而已
數每一種 pair 出現了幾次
詢問的時候,找符合條件的 pair
\(O(m + n^2q)\)
\(l_i \leq r_i < s_i \leq t_i\)
如果把每一種 pair 出現的數量存在一個表格上
那詢問其實是在問一個矩形區域的和
假設 \((i,j)\) 出現 \(c[i][j]\) 次
二維前綴和:\(sum[i][j]= \sum_{i'=1}^i \sum_{j'=1}^j c[i'][j']\)
\(\sum_{i=l}^{r} \sum_{j=s}^t c[i][j] = sum[r][t] \\ - sum[l-1][t] - sum[r][s-1] + sum[l-1][s-1]\)
預處理 \(sum\),詢問只要 \(O(1)\) 的時間
\(O(m + n^2 + q)\)
為什麼上一個 subtask 的 code 不能過這個?
因為會算到重複的
如果 \(l_i \leq a_j \leq r_i \land s_i \leq b_j \leq t_i\)
和 \(l_i \leq b_j \leq r_i \land s_i \leq a_j \leq t_i\)
同時滿足,\(j\) 就會被算到兩次
誰會被算到兩次?
\(\max(l_i,s_i) \leq a_j,b_j \leq \min(r_i,t_i)\)
\(j\) 就會被算到兩次
把這個區域扣掉就好
#include <bits/stdc++.h>
#define StarBurstStream ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
int main(){
StarBurstStream
int n, m;
cin >> n >> m;
vector<vector<int>> a(n + 1, vector<int>(n + 1));
for(int i = 0; i < m; i++){
int u, v;
cin >> u >> v;
if(u > v) swap(u, v);
a[u][v]++;
a[v][u]++;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++) a[i][j] += a[i][j - 1];
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++) a[j][i] += a[j - 1][i];
}
auto calc = [a](int x1, int y1, int x2, int y2){
return a[x2][y2] - a[x2][y1 - 1] - a[x1 - 1][y2] + a[x1 - 1][y1 - 1];
};
int q;
cin >> q;
while(q--){
int l, r, s, t;
cin >> l >> r >> s >> t;
int tl = max(l, s);
int tr = min(r, t);
int ans = calc(l, s, r, t);
if(tl <= tr) ans -= calc(tl, tl, tr, tr) / 2;
cout << ans << "\n";
}
return 0;
}
魔法師測驗 (Mage)
來源: CF 1101D
測資: joylintp
題敘: joylintp
\(m_1=m_2=\ldots=m_n\)
→ 若 \(m_1=1\) 則答案為 \(0\)(和諧度必為 \(1\))
→ 若 \(m_1 \ne 1\) 則答案為樹直徑(和諧度必不為 \(1\))
8 / 100
\(n \le 1000\)
→ 枚舉起點
→ \(O(n)\) DFS 看能走多遠
→ 總複雜度 \(O(n^2)\)
(6 + 14) / 100
和諧度不為 \(1\)
→ 最大公因數大於 \(1\)
→ 所有數字至少有一個共同質因數
\(m_i \le 100\)
→ 枚舉質數(至多 \(25\) 個)
→ 對每個質數建出子圖
→ 找所有子圖中最長的樹直徑
→ 總複雜度 \(O(n \times 25)\)
16 / 100
\(m_i \le 2 \times 10^5\)
→ 質數有 \(17984\) 個
→ 對每個質數建出子圖
→ TLE
\(m_i \le 2 \times 10^5\)
→ \(2 \times 3 \times 5 \times 7 \times 11 \times 17 > 2 \times 10^5\)
→ 每個節點的相異質因數不會超過 \(5\) 個
→ 樹 DP
記錄
轉移
總複雜度 \(O(n\log^2{m})\)
100 / 100
#include<bits/stdc++.h>
using namespace std;
#define F first
#define S second
int ans;
vector<bool> pfac(200001, true), vis;
vector<int> prime, a;
vector<vector<int>> edge;
map<int, int> dfs(int u)
{
vis[u] = true;
map<int, int> sa;
for (int i : prime)
{
if (i * i > a[u])
break;
while (a[u] % i == 0)
sa[i] = 1, a[u] /= i;
}
if (a[u] != 1)
sa[a[u]] = 1;
map<int, pair<int, int>> sb;
for (int i : edge[u])
if (!vis[i])
{
map<int, int> mp = dfs(i);
for (auto p : mp)
if (sa.count(p.F))
{
sa[p.F] = max(sa[p.F], p.S + 1);
if (p.S > sb[p.F].F)
sb[p.F].S = sb[p.F].F, sb[p.F].F = p.S;
else if (p.S > sb[p.F].S)
sb[p.F].S = p.S;
else;
}
}
for (auto p : sa)
ans = max(ans, p.S);
for (auto p : sb)
ans = max(ans, p.S.F + p.S.S + 1);
return sa;
}
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
for (int i = 2; i <= 200000; i++)
{
if (pfac[i])
prime.push_back(i);
for (int j : prime)
{
if (i * j > 200000)
break;
pfac[i * j] = false;
if (i % j == 0)
break;
}
}
int n;
cin >> n, vis.resize(n + 1), a.resize(n + 1), edge.resize(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i];
int u, v;
for (int i = 1; i < n; i++)
cin >> u >> v, edge[u].push_back(v), edge[v].push_back(u);
dfs(1);
cout << ans << '\n';
return 0;
}
刷怪塔 (Monster)
來源: Google Code Jam 2020 Round 2 pA
測資: joylintp
題敘: joylintp
\(a,b \le 1000\)
→ 選擇刷怪塔的次數至多 \(\sqrt{a+b}\) 次
→ 模擬每次選擇刷怪塔
→ 總時間複雜度 \(O(T\sqrt{a+b})\)
12 / 100
\(a=0\)
→ 只會打第二座刷怪塔的怪物
→ \(b \ge \frac{l(l+1)}{2}\)
→ 解不等式或二分搜最大值
→ 總時間複雜度 \(O(T)\) 或 \(O(T\log{b})\)
8 / 100
\(a=b\)
→ 觀察到在怪物數量足夠的情況下,
奇數一定選擇第一座刷怪塔,偶數一定選擇第二座
→ \(a \ge 1+3+\ldots+(2k+1) = (k+1)^2\)
\(b \ge 2+4+\dots+(2k) = (k+1)^2 + k + 1\)
→ 解不等式或二分搜最大值
→ 總時間複雜度 \(O(T)\) 或 \(O(T\log{b})\)
21 / 100
將較多的那座擊殺到怪物數量不超過另一座
接下來兩座刷怪塔必定會輪流使用
→ 作法類似 Subtask 2
→ 總時間複雜度 \(O(T)\) 或 \(O(T\log{b})\)
100 / 100
#include<bits/stdc++.h>
using namespace std;
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int T;
cin >> T;
for (int i = 1; i <= T; i++)
{
long long A, B;
cin >> A >> B;
bool swp = (A <= B);
if (A < B)
swap(A, B);
long long l = 0, r = 2e9;
while (l != r)
{
long long m = (l + r) / 2;
if (m * (m + 1) / 2 > A - B)
r = m;
else
l = m + 1;
}
A -= l * (l - 1) / 2;
if (A > B || (A == B && !swp))
swap(A, B), swp = !swp;
long long x = l - 1;
if (B < x + 1)
{
if (swp)
swap(A, B);
cout << x << ' ' << A << ' ' << B << '\n';
}
else if (A < x + 2)
{
B -= x + 1;
if (swp)
swap(A, B);
cout << x + 1 << ' ' << A << ' ' << B << '\n';
}
else
{
l = x + 2, r = 2e9;
long long Aa = A, Ba = B;
while (l != r)
{
long long m = (l + r) / 2;
long long Al = m, Bl = m;
if (Al % 2 == x % 2)
Bl--;
else
Al--;
long long At = (Al + x + 2) * (Al - x) / 4, Bt = (Bl + x + 1) * (Bl - x + 1) / 4;
if (A < At || B < Bt)
r = m;
else
l = m + 1, Aa = A - At, Ba = B - Bt;
}
if (swp)
swap(Aa, Ba);
cout << l - 1 << ' ' << Aa << ' ' << Ba << '\n';
}
}
return 0;
}
時空旅人之爭 (Time)
來源: 原創
測資: WiwiHo
題敘: joylintp
給一棵樹,複製人大軍會從節點 1 開始擴散,每 2 秒擴散一個節點
有 \(Q\) 筆詢問,詢問求從入侵的時間點開始,從 \(s_i\) 走到 \(t_i\) 的路上
至少要經過幾個有複製人大軍的節點
顯然就是走得越快越好,所以直接走 \(s_i\) 到 \(t_i\) 的簡單路徑
Kronii 到達 \(v\) 的時間:\(dis(s_i, v)\)
複製人大軍到達 \(v\) 的時間: \(2 \times dis(1, v)\)
答案要求有幾個 \(v\) 滿足 \(dis(s_i,v) \geq 2 \times dis(i,v)\)
\(n,q \leq 1000\)
暴力檢查
\(O(nq)\)
\(\forall 1 \leq i < n, v_i = u_{i+1}\)
就是這個樹是一條鏈的意思
如果 \(s_i,t_i\) 都在 \(1\) 的同一側
那麼靠近 \(1\) 的那邊可能會有一段連續的符合條件的節點
也就是這條鏈上會有一邊符合條件、一邊不符合
二分搜找交界點
如果 \(s_i,t_i\) 在 \(1\) 的兩側
那麼符合條件的節點會是中間連續的一段
如果把 \(1\) 左右分開看
就和上一種狀況一樣了
\(O(n + q \log n)\)
除了節點 1 以外,每個節點度數最多 2
也就是說這個樹是從 1 往外伸的一堆鏈
只看 \(s_i\) 和 \(t_i\) 所在的鏈
就跟 Subtask 2 一樣了
Subtask 2,3 都是「把 \(s_i\) 到 \(t_i\) 的路徑,以最靠近 \(1\) 的節點為中心把這條路徑切成兩半」,而切出來的兩條路徑上,都是有一邊的點符合條件(靠近中心的那邊)、一邊不符合
\(s_i\) 到 \(t_i\) 的路徑上離 \(1\) 最近的點就是他們的 LCA
(以 \(1\) 為根)
然後就跟前面一樣了
可以用倍增法二分搜
#include <bits/stdc++.h>
#define StarBurstStream ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define eb(a) emplace_back(a)
using namespace std;
vector<vector<int>> g;
vector<int> in, out, dpt;
int ts = 0;
vector<vector<int>> anc;
void dfs(int now, int p, int d){
in[now] = ts++;
dpt[now] = d;
anc[0][now] = p;
for(int i : g[now]){
if(i == p) continue;
dfs(i, now, d + 1);
}
out[now] = ts++;
}
bool isAnc(int a, int b){
return in[a] <= in[b] && out[a] >= out[b];
}
int getLCA(int a, int b){
if(isAnc(a, b)) return a;
if(isAnc(b, a)) return b;
for(int i = 19; i >= 0; i--){
if(!isAnc(anc[i][a], b)) a = anc[i][a];
}
return anc[0][a];
}
int getDis(int a, int b){
int lca = getLCA(a, b);
return dpt[a] + dpt[b] - 2 * dpt[lca];
}
bool check(int s, int now){
return getDis(s, now) < dpt[now] * 2;
}
void solve(){
int s, t;
cin >> s >> t;
int lca = getLCA(s, t);
if(check(s, lca)){
cout << "0\n";
return;
}
int ans = 0;
int now = s;
for(int i = 19; i >= 0; i--){
if(check(s, anc[i][now])) now = anc[i][now];
}
ans += dpt[now] - dpt[lca] + !check(s, s);
now = t;
for(int i = 19; i >= 0; i--){
if(check(s, anc[i][now])) now = anc[i][now];
}
ans += dpt[now] - dpt[lca] + !check(s, t);
ans--;
cout << ans << "\n";
}
int main(){
StarBurstStream
int n;
cin >> n;
g.resize(n + 1);
in.resize(n + 1);
out.resize(n + 1);
dpt.resize(n + 1);
anc.resize(20, vector<int>(n + 1));
for(int i = 0; i < n - 1; i++){
int u, v;
cin >> u >> v;
g[u].eb(v);
g[v].eb(u);
}
int q;
cin >> q;
dfs(1, 1, 0);
for(int i = 1; i < 20; i++){
for(int j = 1; j <= n; j++){
anc[i][j] = anc[i - 1][anc[i - 1][j]];
}
}
for(int i = 0; i < q; i++){
solve();
}
return 0;
}