link
A C D,怎麼沒過 B?我靠,崩潰了牢弟 qwq
A - Strong Password
B - Make Three Regions
這題。。。我居然用 a[2][j] 然後還真只開了 a[2][N] 的大小,結果 cf test 1 多測只輸出一個結果,沒見過啊,這直接給我幹蒙了啊,又是沒有調出來,陣列空間開小了這種低階錯誤也能犯
C - Even Positions
一開始以為很不可做,之前是看到字串匹配之類的就頭疼,
不過最後十分鐘又看了一下,發現就是簡單貪心,總是選擇較近的空位配對,兩個方向掃一遍,最後三分鐘 A 掉了
D - Maximize the Root
樹上貪心,(dp?倒是不至於,就是簡單的 dfs 加分討
一種很顯然的貪心是,對於一個子樹(根節點非葉節點),總是讓它的所有點權趨於平均,但是保證子樹的根節點比子樹中的其他節點的最小值嚴格不小,這樣在向上回溯時能保證非直接兒子不會非法(即點權減小為負數)
對子樹跟節點點權 \(a_u\),直接兒子中點權最小值 \(a_v\) 分類討論
-
當 \(a_u\geq a_v\),只能直接 \(a_u = a_v\)
-
當 \(a_u = a_v - 1\),這種平均狀態是標準的,不用改變
-
當 \(a_u < a_v\),要使之趨於平均,\(a_u = a_u + \lfloor \frac{a_v - a_u}{2}\rfloor\),這樣就達到要麼 \(a_u = a_v\),要麼 \(a_u = a_v - 1\) 的效果
當然,根節點是特殊情況,因為它不需要考慮父節點,所以直接 \(a_u = a_u + a_v\)
複雜度 \(O(n)\)
code
#include <bits/stdc++.h>
#define re register int
#define int long long
using namespace std;
const int N = 2e5 + 10, inf = 1e9 + 10;
struct Edge
{
int to, next;
}e[N << 1];
int idx, h[N];
int T, n, a[N], in[N];
inline void add(int x, int y)
{
e[++ idx] = (Edge){y, h[x]};
h[x] = idx;
}
void dfs(int u, int fa)
{
int sum = 0, min_son = inf;
for (re i = h[u]; i; i = e[i].next)
{
int v = e[i].to;
if (v == fa) continue;
dfs(v, u);
min_son = min(min_son, a[v]); // 注意是比較更新過後的權值
// sum += a[v];
}
if (u == 1)
{
// cout << "check " << min_son << '\n';
a[u] += min_son;
return;
}
else if (in[u] > 1)
{
if (a[u] >= min_son) a[u] = min_son;
else if (a[u] == min_son - 1) a[u] = a[u];
else
a[u] += floor((double)((min_son - a[u]) / 2));
}
else return;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> T;
while (T --)
{
memset(h, 0, sizeof(h)); idx = 0;
memset(in, 0, sizeof(in));
cin >> n;
for (re i = 1; i <= n; i ++) cin >> a[i];
for (re i = 2; i <= n; i ++)
{
int x; cin >> x;
add(x, i), add(i, x);
in[x] ++, in[i] ++;
}
dfs(1, 0);
// cout << a[3] << '\n';
cout << a[1] << '\n';
}
return 0;
}
E - Level Up
一開始我以為是個 dp?但是不會推
發現題目已經將一個求解問題轉化為判定問題,那考慮往判定方向思考
對於 \(k=x\),發現 \(x\) 越小,對於第 \(i\) 個怪獸而言,它越容易逃跑,因為 \(k\) 越小,玩家期望升級越快,期望等級越比怪物等級高,反之則越不容易逃跑。。。似乎存在二分性
考慮 二分 \(k\)
那我們可以二分一個 \(k\),滿足在這個 \(k\) 下,第 \(i\) 個怪物 剛好不逃跑,這樣詢問時就轉化為簡單的判定了
考慮一個怪物剛好不逃跑的充要條件就是玩家等級不大於怪物等級,最小化 \(k\)
那麼玩家到達第 \(i\) 個位置,至少之前要打過 \(a_i\cdot k\) 個怪獸,即升了 \(a_i\) 級變為 \(a_i + 1>a_i\),這樣怪獸就剛好逃跑(似乎剛好逃跑更自然想到,反正也一樣二分,反過來就可以了
所以,如果之前打過的怪獸數量 \(query\geq a_i\cdot k\),那麼就可以判定怪獸會逃跑
而如何求 \(query\) 呢?注意到我們每次二分出來的值的含義是 剛好使該怪獸不會逃跑的 \(k\),即會打該怪獸
那麼就很顯然了,每次二分完對 \(k\) 值域加貢獻 1,判定時求個字首即可,考慮 線段樹 簡單維護
時間複雜度 \(O(n\log^2 n)\)
code
#include <bits/stdc++.h>
#define re register int
#define lp p << 1
#define rp p << 1 | 1
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
struct Tree
{
int l, r, sum;
}t[N << 2];
int n, q, a[N], not_run[N];
inline void build(int p, int l, int r)
{
t[p].l = l, t[p].r = r;
if (l == r) return;
int mid = (l + r) >> 1;
build(lp, l, mid);
build(rp, mid + 1, r);
}
inline void update(int p, int x, int k)
{
if (t[p].l == x && t[p].r == x)
{
t[p].sum += k;
return;
}
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid) update(lp, x, k);
if (x > mid) update(rp, x, k);
t[p].sum = t[lp].sum + t[rp].sum;
}
inline LL query(int p, int l, int r)
{
if (l <= t[p].l && t[p].r <= r) return t[p].sum;
LL res = 0;
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid) res += query(lp, l, r);
if (r > mid) res += query(rp, l, r);
return res;
}
inline bool check(int x, int k)
{
return (LL)a[x] * k <= query(1, 1, k);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> q;
build(1, 1, n);
for (re i = 1; i <= n; i ++) cin >> a[i];
for (re i = 1; i <= n; i ++)
{
int l = 1, r = n;
while (l < r)
{
int mid = (l + r) >> 1;
if (check(i, mid)) l = mid + 1;
else r = mid;
}
update(1, l, 1);
not_run[i] = l;
}
while (q --)
{
int x, k; cin >> x >> k;
cout << (k < not_run[x] ? "No" : "Yes") << '\n';
}
return 0;
}
F - Chips on a Line
*2700 的 dp?感覺很不可做,溜了溜了 ~