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:Recording the Moolympics

前置题目

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\) 个节目,每个节目都有开始时间 \(s_i\) 和结束时间 \(t_i\)。至多有 \(2\)​ 个节目同时在录制,求最多能录制多少节目。

思路

首先还是按 \(t\) 排序。

再来考虑对于节目 \(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\),那就只能用结束时间为 \(last2\) 的录音机来录制了。

\(\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:Wormhole Sort

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:BLO

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{完结撒花!}\)

posted @ 2021-08-10 18:03  mango09  阅读(43)  评论(5编辑  收藏  举报
-->