1. 程式人生 > 實用技巧 >2020-11-30 考試總結

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\)

用並查集連起來。然後維護編號最小點即可。時間複雜度 \(\Theta(n\log^2 n)\)

但是這樣並不能過,考慮去掉二分,可以看出,每次當我們走不動的時候我們把答案+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}\)

  1. \(s_{i-1}\)\(s_i\) 的祖先,且 $s_{i-1} != s_i $

  2. \(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;
}