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

2020-11-27 考試總結

今天又炸了,沒有什麼好說的了。

T1 bins

水題,就沒有什麼好說的了,\(\Theta (n\log m)\) 顯然。

T2 inversions

題目傳送門

考試的時候被這個題區分了,主要原因是因為並沒有想到一層傳懶標記的話可以把一層卡成一個整體。

不難想到我們可以建出一個類似於線段樹的玩意,然後每個節點的貢獻就是兩個子塊之間的逆序對個數(其實就是歸併)。

你發現翻轉了之後父子關係不會變,於是我們就可以認為就是加逆序對變為了加順序對。於是直接一開始歸併的時候求一下逆序對和順序對就好了。

時間複雜度 \(\Theta((m+2^n)n)\)

\(\texttt{Code}\)


#include <bits/stdc++.h>
using namespace std;

#define Int register int
#define INF 0x7f7f7f7f
#define MAXN 1100005

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,m,th,tot,a[MAXN],s[MAXN];

#define ll long long
ll ans[2][25],get[2][MAXN << 2];

void divide (int l,int r,int dep){
	if (l >= r) return ;
	int mid = (l + r) >> 1;
	divide (l,mid,dep + 1),divide (mid + 1,r,dep + 1);
	int st1 = l,st2 = mid + 1,sam1 = 0,sam2 = 0,tot = 0;ll get1 = 0,get2 = 0;
	while (st1 <= mid || st2 <= r){
		if ((st1 <= mid && st2 <= r && a[st1] <= a[st2]) || (st1 <= mid && st2 > r)) 
		s[++ tot] = a[st1 ++],sam1 = (st1 >= l + 2 && a[st1 - 1] == s[st1 - 2]) ? sam1 + 1 : 1,get1 += (st2 - mid - 1) - (a[st1 - 1] == a[st2 - 1] ? sam2 : 0);
		else 
		s[++ tot] = a[st2 ++],sam2 = (st2 >= mid + 3 && a[st2 - 1] == a[st2 - 2]) ? sam2 + 1 : 1,get2 += (st1 - l) - (a[st1 - 1] == a[st2 - 1] ? sam1 : 0);	
	}
	ans[0][dep] += get1,ans[1][dep] += get2;
	for (Int i = l;i <= r;++ i) a[i] = s[i - l + 1];
}

signed main(){
	read (n);
	for (Int i = 1;i <= (1 << n);++ i) read (a[i]);
	divide (1,(1 << n),1);
	read (m); 
	for (Int i = 1,x;i <= m;++ i){
		read  (x);int qnow = n - x + 1;
		for (Int j = qnow;j <= n;++ j) swap (ans[0][j],ans[1][j]);
		ll ansnow = 0;for (Int j = 1;j <= n;++ j) ansnow += ans[0][j];
		write (ansnow),putchar ('\n');
	}
	return 0;
}

T3 [BalticOI 2010 Day2] Candies

題目傳送門

發現 P 很好求,直接揹包就好了。揹包可以撤銷。

考慮怎麼求 Q。不難發現如果你設可行狀態為 \(S\),你就是要求 \(x\) 使得 \(S\)\(S<<x\) 取併為 \(0\)。考試的時候就是這個玩意不知道怎麼搞(但是 Reanp 說她一眼秒掉 Q 不知道怎麼求 P,我尋思是不是搞 ACM 賽制就無敵了啊?)其實你發現不合法條件可以寫成:

\[\sum_{i\in S} B_i+Q=\sum_{i\in T} B_i \]

\[\Rightarrow Q=\sum_{i\in T} B_i-\sum_{i\in S} B_i \]

然後你發現其實就是對 \(\{B_i,-B_i\}\) 做揹包就好了。

時間複雜度 \(\Theta(n^2w^2/\omega)\)

T4 sheep

題目傳送門

題目大意

一棵樹有 \(n\) 個節點,這些節點用 \(1\)\(n\) 的整數編號。樹上住著一群羊,一共有 \(k\) 只。每個節點最多可容納一隻羊住。

聰明的牧羊人意識到愛吃羊的狼遲早會學習如何爬樹。為了保護羊,需要將牧羊人佈置在一些節點上,使得每隻羊至少有一位牧羊人去守護。

每個牧羊人僅會保護離他最近的那幾只羊(如果有多個距離最近的,則全部都會保護)。羊和牧羊人之間的距離等於羊所在節點與牧羊人所在的節點之間的唯一路徑上的節點數。另外,牧羊人可以與羊同在一個節點上(當然,在那種情況下,他只會保護那一隻羊)。

編寫程式計算牧羊人的最小數量,以便每隻羊都受到至少一個牧羊人的保護。

\(n,m\le 5\times 10^5\)

思路

為什麼會有人出這種不可做樹上貪心啊!!!

我們設 \(d_u\) 表示點 \(u\) 到周圍最近的羊的距離,這個可以 BFS 或者直接換根求出。

可以發現的是,我們如果牧羊人 \(x\) 控制羊 \(y\) ,那麼 \(x\sim y\) 的路徑上必須滿足 \(d_i\) 嚴格單調遞減。因為不難發現存在邊 \((u,v)\),且 \(d_u>d_v\),那麼 \(u\) 一定不比 \(v\) 劣。因為 \(u\) 的控制範圍一定不比 \(v\) 小。

我們還可以發現的是,我們一個子樹外的牧羊人其實也有可能且儘可能控制到這個子樹深度最淺的羊。

於是,我們可以考慮樹上貪心。每次考慮將當前節點的子樹合併的時候,可以看出如果一個子樹還沒有合併完成,那麼一定到兒子的距離都是最小且相同的。於是我們可以先考慮一個子樹外的牧羊人是否可以控制這個子樹深度最淺的羊,如果在 \(v\) 放一定更優,那麼我們肯定就會在 \(v\) 放。否則的話我們就可以留著等祖先節點考慮。

時間複雜度 \(\Theta(n)\)

\(\texttt{Code}\)

#include <bits/stdc++.h>
using namespace std;

#define Int register int
#define INF 0x7f7f7f7f
#define MAXN 500005

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');}

bool tag[MAXN];
vector <int> G[MAXN];
int n,k,ans,mn[MAXN],dis[MAXN];

void dfs1 (int u,int fa){for (Int v : G[u]) if (v ^ fa) dfs1 (v,u),mn[u] = min (mn[u],mn[v] + 1);}
void dfs2 (int u,int fa){for (Int v : G[u]) if (v ^ fa) mn[v] = min (mn[v],mn[u] + 1),dfs2 (v,u);}
void dfs3 (int u,int fa){
	dis[u] = -1;
	for (Int v : G[u]) if (v != fa) dfs3 (v,u),dis[u] = max (dis[u],dis[v] - 1);
	for (Int v : G[u]) if (v != fa && tag[v] && mn[v] >= mn[u]) if (dis[u] < mn[v] + 1) ans ++,dis[u] = max (dis[u],mn[v] - 1);
	if (mn[u] == 0 && dis[u] < 0) tag[u] = 1;
	for (Int v : G[u]) if (v != fa && tag[v] && mn[v] < mn[u]) if (dis[u] < mn[v] + 1) tag[u] = 1; 
}

signed main(){
	read (n),read (k);
	for (Int i = 2,u,v;i <= n;++ i) read (u),read (v),G[u].push_back (v),G[v].push_back (u);
	for (Int i = 1;i <= n;++ i) mn[i] = n + 1;
	for (Int i = 1,u;i <= k;++ i) read (u),mn[u] = 0;
	dfs1 (1,0),dfs2 (1,0),dfs3 (1,0);
	write (ans + tag[1]),putchar ('\n'); 
	return 0;
}