1. 程式人生 > 其它 >8月10日模擬賽題解

8月10日模擬賽題解

前言

這次模擬賽應該是暑假以來最水的一場了,然而本來至少 \(210\) 的分數愣是被我弄成了 \(141\),原因竟然是:

const int MAXM = 5e5 + 5;

struct edge
{
	int to, nxt;
}e[MAXN << 1]; //是 MAXM!!!
//------------------------------------------------------
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++) //是 <=m!!!
{
	int u, v;
	scanf("%lld%lld", &u, &v);
	add(u, v);
    add(v, u);
}

還是太粗心了。

\(\text{Solution}\)

T1:可持久化變數

題意

讓你維護一個變數,初始值為 \(0\),需要支援以下四種操作:

  1. \(\operatorname{ADD}(x)\):將變數的值增加 \(x\)
  2. \(\operatorname{SUB}(x)\):將變數的值減少 \(x\)​。
  3. \(\operatorname{SET}(x)\):將變數的值變為 \(x\)​。
  4. \(\operatorname{BACK}(x)\):回到之前的第 \(x\) 個操作前,例如 \(\operatorname{BACK}(3)\) 表示以當前操作為基準回到前 \(3\)​​​​​​ 次操作
    的狀態。

輸出每次操作後當前數的值。

思路

簽到題竟然有人寫炸了,直接模擬即可。

\(\text{Code}\)

#include <iostream>
#include <cstdio>
using namespace std;

int a[1000005];

int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		char s[7];
		int x;
		scanf("%s%d", s, &x);
		switch (s[1])
		{
			case 'D':
				a[i] = a[i - 1] + x;
				break;
			case 'U':
				a[i] = a[i - 1] - x;
				break;
			case 'E':
				a[i] = x;
				break;
			case 'A':
				a[i] = a[i - x - 1]; //注意是第 x 次操作前,即第 (x - 1) 次操作後
				break;
		}
		printf("%d ", a[i]);
	}
	return 0;
}
/*
Input
7
ADD 2
SUB 3
BACK 1
BACK 1
BACK 1
BACK 2
SET 5

Output
2 -1 2 -1 2 2 5
*/

T2

前置題目

P1803 凌亂的yyy / 線段覆蓋P2970 [USACO09DEC]Selfish Grazing S(雙倍經驗)

本題題意

\(n\)​ 個比賽,每個比賽都有開始時間和結束時間。不能同時參加 \(\ge2\) 個比賽,求最多能參加多少比賽。

本題思路

一個顯然的貪心就是結束時間越晚越好(這就不用證明了吧)。

本題 \(\text{Code}\)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 1e6 + 5;

struct node
{
	int s, t;
	bool operator <(const node &x)const
	{
		return x.t > t;
	}
}a[MAXN];

int main()
{
	int n, last = 0, ans = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d%d", &a[i].s, &a[i].t);
	}
	sort(a + 1, a + n + 1);
	for (int i = 1; i <= n; i++)
	{
		if (last <= a[i].s) //能選則選
		{
			last = a[i].t;
			ans++;
		}
	}
	printf("%d\n", ans);
	return 0;
}

P2255 [USACO14JAN]Recording the Moolympics S

題意

\(n\) 個節目,每個節目都有開始時間和結束時間。至多有 \(2\)​ 個節目同時在錄製,求最多能錄製多少節目。

思路

首先還是按結束時間排序。

再來考慮對於節目 \(i\)。應該用哪個錄音機錄製。

\(last1,last2\) 分別表示兩臺錄音機的結束時間,且 \(last1\ge last2\),當 \(last1<last2\) 時直接 \(\operatorname{swap}\) 就行了。

\(last1\le s_i\)​,則 \(last2\le last1\le s_i\)​。那麼兩臺錄音機都能錄製節目 \(i\)​,但由於 \(s_i-last1\le s_i-last2\)​,所以用結束時間為 \(last2\)​ 的錄音機錄製,中間的空閒時間會比用結束時間為 \(last1\)​ 的錄音機錄製的空閒時間要長,作為一個萬惡的資本家+險惡的地主,我們當然會讓結束時間為 \(last1\)​​​ 的錄音機來錄製,說白了就是 少讓它休息(這樣弄久了錄音機會壞的吧)。

\(last1>s_i\)\(last2\le s_i\),那就只能放

\(\text{Code}\)

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

struct node
{
	int s, t;
	bool operator <(const node &x)const
	{
		return x.t > t;
	}
}a[200];

int main()
{
	int n, ans = 0, last1 = 0, last2 = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d%d", &a[i].s, &a[i].t);
	}
	sort(a + 1, a + n + 1);
	for (int i = 1; i <= n; i++)
	{
		if (last1 <= a[i].s)
		{
			last1 = a[i].t;
			ans++;
		}
		else if (last2 <= a[i].s)
		{
			last2 = a[i].t;
			ans++;
		}
		if (last1 < last2)
		{
			swap(last1, last2);
		}
	}
	printf("%d", ans);
	return 0;
}  

T3

P6004 [USACO20JAN] Wormhole Sort S

題意

\(n\) 頭編號為 \(1 \sim n\) 的奶牛,一開始奶牛 \(i\) 位於位置 \(p_i\)\(p_1\sim p_n\)\(1\sim n\) 的一個排列)。有 \(m\) 個編號為 \(1\sim m\) 的蟲洞,蟲洞 \(i\) 雙向連線了位置 \(u_i\)\(v_i\),寬度為 \(w_i\)。兩頭位於一個蟲洞兩端的奶牛可以選擇通過蟲洞交換位置。奶牛們需要反覆進行這樣的交換,直到奶牛 \(i\) 位於位置 \(i\)​。求用來排序的蟲洞寬度的最小值的最大值是多少。保證奶牛們能排好序。如果奶牛們不需要用任何蟲洞來排序,輸出 \(-1\)

思路

看到

最小值的最大值

立馬想到二分(已經快成條件反射了)。

二分下標和答案應該都可以,我用了二分答案。

\(1\le w_i\le10^9\),所以​取 \(l=1,r=10^9,mid=\frac{l+r+1}{2}\),每次 \(\operatorname{check}(mid)\) 將所有 \(w_i\ge mid\) 都在 \(u_i\)\(v_i\) 間連一條邊。

然後判斷每個 \(i\)​​​ 和 \(p_i\)​​​ 是否在一個連通塊內,若在,則說明從 \(i\)​​​ 出發走若干條邊能到達 \(p_i\)​​​,即從 \(i\)​​​ 開始交換若干次能交換到 \(p_i\)(請各位感性理解一下)。

然後就沒有然後了。

然後,記得判 \(-1\)

\(\text{Code}\)

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e5 + 5;

struct edge
{
	int from, to, dis;
}e[MAXN];

int n, m;
int fa[MAXN], pos[MAXN];

void init()
{
	for (int i = 1; i <= n; i++)
	{
		fa[i] = i;
	}
}

int find(int x)
{
	if (x == fa[x])
	{
		return x;
	}
	return fa[x] = find(fa[x]);
}

void merge(int x, int y)
{
	x = find(x), y = find(y);
	if (x != y)
	{
		fa[y] = x;
	}
}

bool check(int w)
{
	init(); //記得清空
	for (int i = 1; i <= m; i++)
	{
		if (e[i].dis >= w)
		{
			merge(e[i].from, e[i].to); //連邊
		}
	}
	for (int i = 1; i <= n; i++)
	{
		if (find(i) != find(pos[i])) //不在就不行
		{
			return false;
		}
	}
	return true;
}

int main()
{
	scanf("%d%d", &n, &m);
	bool flag = true;
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", pos + i);
		if (i != pos[i])
		{
			flag = false; //判-1
		}
	}
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d%d", &e[i].from, &e[i].to, &e[i].dis);
	}
	if (flag)
	{
		puts("-1");
		return 0;
	}
	int l = 1, r = 1e9;
	while (l < r)
	{
		int mid = (l + r + 1) >> 1;
		if (check(mid))
		{
			l = mid;
		}
		else
		{
			r = mid - 1;
		}
	}
	printf("%d", l);
	return 0;
}

T4

P3469 [POI2008]BLO-Blockade

題意

給定一張有 \(n\) 個點 \(m\)​ 條邊的無向圖,回答若把所有與點 \(u\) 連線的邊(不包括點 \(u\))去掉後圖中會有多少個 有序 點對 \(<x,y>\) 不能互相到達。保證給出的圖連通。

思路

割點 \(+\) 樹形 \(\rm dp\)

考慮 \(u\)

  1. \(u\) 不是割點:那麼只有 \(u\)​ 與剩下的點不能互相到達,注意是有序點對,所以有 \(2(n-1)\) 組。
  2. \(u\) 是割點:此時圖分成了若干個連通塊,主要可以分成以下 \(3\) 類:
    1. \(u\)​​ 的每個滿足 \(dfn_u\le low_v\)​ 的兒子以及各自的子樹:設共有 \(k\)​ 個滿足條件的兒子,分別為 \(son_1\sim son_k\)​,節點 \(x\)​ 的子樹內共有 \(siz_x\)​ 個節點。則兒子 \(son_i\)​ 對答案的貢獻為 \(siz_{son_i}\times(n-siz_{son_i})\)​​。​
    2. \(u\):他對於答案的貢獻為 \((n-1)\)
    3. 剩下的部分:對答案的貢獻為 \((n-1-\sum\limits_{i=1}^k siz_{son_i})\times(1+\sum\limits_{i=1}^k siz_{son_i})\)

綜上,答案為 $$\boxed{\sum\limits_{i=1}^k siz_{son_i\times(n-siz_{son_i})}+(n-1)+(n-1-\sum\limits_{i=1}^k siz_{son_i})\times(1+\sum\limits_{i=1}^k siz_{son_i})}$$​。

\(\text{Code}\)

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;

const int MAXN = 1e5 + 5;
const int MAXM = 5e5 + 5;

int n, m, cnt, Time;
int head[MAXN], dfn[MAXN], low[MAXN], siz[MAXN], ans[MAXN];
bool cut[MAXN];

struct edge
{
	int to, nxt;
}e[MAXM << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

void tarjan(int u)
{
	dfn[u] = low[u] = ++Time;
	siz[u] = 1;
	int flag = 0, sum = 0;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
			siz[u] += siz[v]; //樹形 dp 求 siz(其實感覺不算)
			if (dfn[u] <= low[v])
			{
				flag++;
				if (u != 1 || flag > 1)
				{
					cut[u] = true;
				}
				sum += siz[v]; //siz 的和
				ans[u] += siz[v] * (n - siz[v]); //子樹的貢獻
			}
		}
		else
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (cut[u])
	{
		ans[u] += (n - 1) + (n - 1 - sum) * (1 + sum);
	}
	else
	{
		ans[u] = 2 * (n - 1);
	}
}

signed main()
{
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%lld%lld", &u, &v);
		add(u, v);
		add(v, u);
	}
	tarjan(1);
	for (int i = 1; i <= n; i++)
	{
		printf("%lld\n", ans[i]);
	}
	return 0;
}

\(\Large{完結撒花!}\)