2020-11-30 考試總結
本來以為不會掛分了,但還是掛了45。。。本來不掛前面幾名就穩了。
T1 ZZH的遊戲
Description
ZZH 在和 GVZ 玩遊戲。
ZZH 和 GVZ 各有一棵樹,每棵樹都有 \(n\) 個點。
兩棵樹上各自有一枚棋子。ZZH 的棋子初始在它樹上的點 \(s\) ,GVZ 的棋子初始在樹上的點 \(t\) 。
兩人輪流操作。一個人操作時,可以選擇不移動自己樹上的棋子,也可以選擇將自己樹上的棋子移動到自己樹上相鄰的一個點。
當兩枚棋子都在所在樹的點 \(1\) 上時,遊戲結束。遊戲的分數是所有時刻中,兩個棋子所在的點編號的和的最大值。
ZZH 和 GVZ 會合作讓遊戲結束時的分數儘量小。兩人都極其聰明,因此兩人都會採用最優策略。
ZZH 和 GVZ 一共會進行 \(T\) 次遊戲,每次遊戲的樹與之前不同, \(n\) 也不一定相同,但兩人的樹的點數一定相等。
你需要求出在每一次遊戲中,遊戲結束時的分數的最小值。
\(\sum{n}\leq 10^6,T\leq 10^4\)
Solution
不難想到,可以二分答案,然後我們貪心得想的話,肯定時u先走到能走到的編號最小的點,然後v走到能走到的編號最小的點,然後u走到能走到的編號最小的點,v走到能走到的編號最小的點,如此迴圈往復,判斷最後兩個點是否都可以走到1即可。
考慮如何優化,不難看出我們可以對於每一條邊 \((a,b)\) 按 \(\max(a,b)\) 排序,每次可以走的話就把 \(a,b\)
但是這樣並不能過,考慮去掉二分,可以看出,每次當我們走不動的時候我們把答案+1即可。時間複雜度 \(\Theta(n\log n)\)。
\(\texttt{Code}\)
#include <bits/stdc++.h> using namespace std; #define Int register int #define MAXN 1000005 template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;} template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');} int tim,n,S,T; struct Edge{ int u,v,w; Edge(){} Edge (int _u,int _v,int _w){u = _u,v = _v,w = _w;} bool operator < (const Edge &p)const{return w < p.w;} }e1[MAXN],e2[MAXN]; int lim,st[2],mi[2],miget1[MAXN],miget2[MAXN]; int fa1[MAXN],fa2[MAXN]; int findSet1 (int x){return fa1[x] == x ? x : fa1[x] = findSet1 (fa1[x]);} int findSet2 (int x){return fa2[x] == x ? x : fa2[x] = findSet2 (fa2[x]);} void unionSet1 (int u,int v){ int mi1 = miget1[findSet1 (u)],mi2 = miget1[findSet1 (v)]; fa1[findSet1 (u)] = findSet1 (v), miget1[fa1[v]] = min (mi1,mi2); } void unionSet2 (int u,int v){ int mi1 = miget2[findSet2 (u)],mi2 = miget2[findSet2 (v)]; fa2[findSet2 (u)] = findSet2 (v), miget2[fa2[v]] = min (mi1,mi2); } void modify (int now,int get){ if ((get == 0 && findSet1 (now) == findSet1 (S))) mi[get] = min (mi[get],miget1[findSet1 (now)]); else if (get == 1 && findSet2 (now) == findSet2 (T)) mi[get] = min (mi[get],miget2[findSet2 (now)]); } void Work (){ lim = S + T,st[0] = st[1] = 1,mi[0] = S,mi[1] = T; for (Int i = 1;i <= n;++ i) fa1[i] = fa2[i] = miget1[i] = miget2[i] = i; int las[2] = {1,1}; for (Int now = 0;now <= 4 * n;++ now){ int get = now & 1; while (st[get] <= n - 1 && (get == 0 ? e1[st[get]].w : e2[st[get]].w) <= lim - mi[get ^ 1]){ if (get == 0) unionSet1 (e1[st[get]].u,e1[st[get]].v),modify (e1[st[get]].u,0),modify (e1[st[get]].v,0); else unionSet2 (e2[st[get]].u,e2[st[get]].v),modify (e2[st[get]].u,1),modify (e2[st[get]].v,1); st[get] ++; } if (findSet1 (1) == findSet1 (S) && findSet2 (1) == findSet2 (T)){ write (lim),putchar ('\n'); return ; } if (now & 1){ if (st[0] == las[0] && st[1] == las[1]) lim ++; else las[0] = st[0],las[1] = st[1]; } } write (lim),putchar ('\n'); } signed main(){ // freopen ("game.in","r",stdin); // freopen ("game.out","w",stdout); read (tim); while (tim --> 0){ read (n); for (Int i = 2,u,v;i <= n;++ i) read (u),read (v),e1[i - 1] = Edge (u,v,max (u,v)); for (Int i = 2,u,v;i <= n;++ i) read (u),read (v),e2[i - 1] = Edge (u,v,max (u,v)); read (S),read (T),sort (e1 + 1,e1 + n),sort (e2 + 1,e2 + n); Work (); } return 0; }
T2 ZZH與揹包
Description
ZZH 有一個揹包。
ZZH有 n 個物品,第 i 個物品的體積為 \(v_i\)。
ZZH 要去學校,她想帶 n 個物品中的一些去學校。為了使揹包不過於空,放入揹包中的物品體積總和不能小於 l 。因為揹包有容量上限,所以放入揹包中的物品體積總和不能大於 r。
ZZH 想知道,她能帶去學校的物品的集合一共有多少種。ZZH覺得這個問題太簡單了,於是她把這個問題交給了你。
ZZH一共要去 q 次學校,因為ZZH還有一些必須帶的東西(例如顯示卡),所以每次的 l,r 會改變。你需要對於每一個給出的 l,r 求出答案。
\(1\leq n \leq 40, q \leq 500, v_i \leq 10^9, 1 \leq l_i \leq r_i \leq \sum{v_i}\)
Solution
不難看出我們可以折半搜尋,然後你每次直接從前往後掃一遍就好了。
時間複雜度 \(\Theta(q2^{n/2})\)。
\(\texttt{Code}\)
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define int long long
#define MAXN 45
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int n,q,v[MAXN];
vector <int> get1,get2;
int my_lowerbound (vector <int> &S,int v){
int l = 0,r = S.size() - 1,ans = r + 1;
while (l <= r){
int mid = (l + r) >> 1;
if (S[mid] >= v) ans = mid,r = mid - 1;
else l = mid + 1;
}
return ans;
}
signed main(){
read (n),read (q);
int all = 1ll << n;
for (Int i = 1;i <= n;++ i) read (v[i]);
int len = (n <= 32 ? n / 3 : n / 2);
for (Int S = 0;S < (1ll << len);++ S){
int now = 0;
for (Int i = 1;i <= len;++ i) if (S >> i - 1 & 1) now += v[i];
get1.push_back (now);
}
sort (get1.begin(),get1.end());
len = n - len;
for (Int S = 0;S < (1ll << len);++ S){
int now = 0;
for (Int i = 1;i <= len;++ i) if (S >> i - 1 & 1) now += v[(n <= 32 ? n / 3 : n / 2) + i];
get2.push_back (now);
}
sort (get2.begin(),get2.end());
while (q --> 0){
int l,r,ans = 0;read (l),read (r);int ed = my_lowerbound (get2,l - get1[0]);
for (Int i = 0;i < get1.size() && get1[i] < l;++ i){
int now = get1[i];
while (ed && get2[ed - 1] >= l - now) -- ed;
ans += ed;
}
int L = 0,R = get1.size() - 1,res = 0;
while (L <= R){
int mid = (L + R) >> 1;
if (my_lowerbound (get2,r - get1[mid] + 1) == get2.size()) res = mid,L = mid + 1;
else R = mid - 1;
}
ed = get2.size();ed = my_lowerbound (get2,r - get1[0] + 1);
for (Int i = res;i < get1.size();++ i){
int now = get1[i];
while (ed && get2[ed - 1] >= r - now + 1) -- ed;
ans += get2.size() - ed;
}
write (all - ans),putchar ('\n');
}
return 0;
}
T3 ZZH與計數
Description
ZZH 喜歡計數。
ZZH 有很多的數,經過統計,ZZH一共有 \(v_0\) 個 0 ,\(v_1\) 個 1,...,\(v_{2^{n}-1}\) 個 \(2^{n} - 1\) 。因為一些原因,ZZH 只有這 \(2^{n}\) 種數。
ZZH 和 GVZ 要對這些數進行 m 次操作。每一次操作由一個人進行。每一次,有 p 的概率由 ZZH 操作, 1 - p 的概率由 GVZ 操作。
兩人進行操作的時候都會依次操作每一個數。對於一個數 s ,如果 ZZH 對這個數進行操作,她會在 \(0,1,...,2^{n} - 1\) 中找出所有的 t,滿足 t or s = s,然後將 s 等概率隨機變成找出的 t 中的一個。
如果 GVZ 對這個數進行操作,她會在 \(0,1,...,2^{n} - 1\) 中找出所有的 t,滿足 t and s = s ,然後將等概率隨機變成找出的 t 中的一個。(這裡的and/or指二進位制與/或操作)
因為操作需要非常長的時間,她們想要知道所有操作結束後,對於每一個 i ,i 的個數的期望。因為期望值可能不是整數,所以她們想知道期望值模 的結果。
因為她們覺得這個問題太簡單了,於是她們把這個問題交給了你。
Solution
還不會,先咕著。
話說本來以為這個題區分度會很大,不過似乎都是暴力。(為什麼沒人寫30啊???
T4 ZZH的旅行
Description
給一棵有根樹,\(1\) 為根。對於每個點 \(x\) ,求出對於滿足如下條件的序列 \({s_1,...,s_k}\)
-
\(s_{i-1}\) 是 \(s_i\) 的祖先,且 $s_{i-1} != s_i $
-
\(s_1 = x\)
中,$ \sum_{i=2}^k(a_{s_{i-1}} - dis(s_{i-1},s_i))b_{s_i} $ 的最大值。
\(n \leq 10^6, 0 \leq a_i,b_i,d_i \leq 10^9\)
Solution
不難看出,設 \(f_u\) 為從 \(u\) 的答案,那麼存在轉移式:
\[f_u=\max_{v\in tree_u}\{f_v+(a_u+dep_u-dep_v)\times b_v\} \]然後你發現這個東西我們可以當成一個斜率為 \(b_v\),截距為 \(f_v-dep_v\times b_v\) 的直線在 \(x=a_u+dep_u\) 的取值。然後說白了你就是要維護一堆直線,然後問在 \(x\) 的取值的最高點。
於是問題就變成了李超線段樹合併板子。
時間複雜度 \(\Theta(n\log n)\),空間動態開點回收空間之後可以做到 \(\Theta(n)\)。
\(\texttt{Code}\)
#include <bits/stdc++.h>
using namespace std;
#define get yourmothersget
#define Int register int
#define int long long
#define MAXN 1000005
char buf[1<<17],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<17,stdin),p1==p2)?EOF:*p1++)
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int un,tmp[MAXN];
int n,tot,siz,K[MAXN],B[MAXN];
signed son[MAXN * 31][2],tree[MAXN * 31];
inline int f (int x,int l){return tmp[l] * K[x] + B[x];}
inline int newnode (){return ++ siz;}
inline void modify (signed &id,int l,int r,int x){
if (!id) id = newnode ();
if (l == r){
if (f (x,l) > f(tree[id],l)) tree[id] = x;
return ;
}
int mid = (l + r) >> 1;
if (K[tree[id]] < K[x]){
if (f (tree[id],mid) < f (x,mid)) modify (son[id][0],l,mid,tree[id]),tree[id] = x;
else modify (son[id][1],mid + 1,r,x);
}
else{
if (f (tree[id],mid) > f (x,mid)) modify (son[id][0],l,mid,x);
else modify (son[id][1],mid + 1,r,tree[id]),tree[id] = x;
}
}
inline int query (int id,int l,int r,int x){
if (!id) return 0;
if (l == r) return f (tree[id],x);
int mid = (l + r) >> 1;
if (x <= mid) return max (query (son[id][0],l,mid,x),f (tree[id],x));
else return max (query (son[id][1],mid + 1,r,x),f (tree[id],x));
}
inline void clear (int u){
son[u][0] = son[u][1] = tree[u] = 0;
}
inline void Merge (int l,int r,signed &u,int v){
if (!u || !v) return u = u + v,void ();
if (l == r){
if (f (tree[u],l) < f (tree[v],l)) tree[u] = tree[v];
if (v) clear (v);
return ;
}
int mid = (l + r) >> 1;
if (tree[v]) modify (u,l,r,tree[v]);
Merge (l,mid,son[u][0],son[v][0]);
Merge (mid + 1,r,son[u][1],son[v][1]);
if (v) clear(v);
}
struct Edge{
int v,w;
};
vector <Edge> G[MAXN];
#define son bitch
int a[MAXN],b[MAXN],dep[MAXN],get[MAXN],size[MAXN];
signed rt[MAXN],son[MAXN];
inline void dfs (int u,int fa){
size[u] = 1;
for (Edge to : G[u]) if (to.v ^ fa){
int v = to.v,w = to.w;
dep[v] = dep[u] + w,dfs (v,u),size[u] += size[v];
if (size[v] > size[son[u]]) son[u] = v;
}
}
inline void maintain (int u,int fa){
if (son[u]) maintain (son[u],u),rt[u] = rt[son[u]];
for (Edge to : G[u]) if (to.v != fa && to.v != son[u]) maintain (to.v,u),Merge (1,un,rt[u],rt[to.v]);
int id = lower_bound (tmp + 1,tmp + un + 1,a[u] + dep[u]) - tmp;
get[u] = query (rt[u],1,un,id),K[++ tot] = b[u],B[tot] = get[u] - dep[u] * b[u],modify (rt[u],1,un,tot);
}
signed main(){
read (n);
for (Int i = 1;i <= n;++ i) read (a[i]),read (b[i]);
for (Int i = 2,u,v,w;i <= n;++ i) read (u),read (v),read (w),G[u].push_back (Edge {v,w}),G[v].push_back (Edge {u,w});
dfs (1,0);for (Int i = 1;i <= n;++ i) tmp[i] = a[i] + dep[i];sort (tmp + 1,tmp + n + 1),un = unique (tmp + 1,tmp + n + 1) - tmp - 1;
maintain (1,0);for (Int i = 1;i <= n;++ i) write (get[i]),putchar ('\n');
return 0;
}