8月10日模拟赛题解

前言

这次模拟赛应该是暑假以来最水的一场了,然而本来至少 210 210 210 的分数愣是被我弄成了 141 141 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);
}

还是太粗心了。

Solution \text{Solution} Solution

T1:可持久化变量

题意

让你维护一个变量,初始值为 0 0 0,需要支持以下四种操作:

  1. ADD ⁡ ( x ) \operatorname{ADD}(x) ADD(x):将变量的值增加 x x x
  2. SUB ⁡ ( x ) \operatorname{SUB}(x) SUB(x):将变量的值减少 x x x​。
  3. SET ⁡ ( x ) \operatorname{SET}(x) SET(x):将变量的值变为 x x x​。
  4. BACK ⁡ ( x ) \operatorname{BACK}(x) BACK(x):回到之前的第 x x x 个操作前,例如 BACK ⁡ ( 3 ) \operatorname{BACK}(3) BACK(3) 表示以当前操作为基准回到前 3 3 3​​​​​​ 次操作 的状态。

输出每次操作后当前数的值。

思路

签到题竟然有人写炸了,直接模拟即可。

Code \text{Code} 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 n n​ 个比赛,每个比赛都有开始时间和结束时间。不能同时参加 ≥ 2 \ge2 2 个比赛,求最多能参加多少比赛。

本题思路

一个显然的贪心就是结束时间越晚越好(这就不用证明了吧)。

本题 Code \text{Code} 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 n n 个节目,每个节目都有开始时间 s i s_i si 和结束时间 t i t_i ti。至多有 2 2 2​ 个节目同时在录制,求最多能录制多少节目。

思路

首先还是按结束时间排序。

再来考虑对于节目 i i i。应该用哪个录音机录制。

l a s t 1 , l a s t 2 last1,last2 last1,last2 分别表示两台录音机的结束时间,且 l a s t 1 ≥ l a s t 2 last1\ge last2 last1last2,当 l a s t 1 < l a s t 2 last1<last2 last1<last2 时直接 swap ⁡ \operatorname{swap} swap 就行了。

l a s t 1 ≤ s i last1\le s_i last1si​,则 l a s t 2 ≤ l a s t 1 ≤ s i last2\le last1\le s_i last2last1si​。那么两台录音机都能录制节目 i i i​,但由于 s i − l a s t 1 ≤ s i − l a s t 2 s_i-last1\le s_i-last2 silast1silast2​,所以用结束时间为 l a s t 2 last2 last2​ 的录音机录制,中间的空闲时间会比用结束时间为 l a s t 1 last1 last1​ 的录音机录制的空闲时间要长,~~作为一个万恶的资本家+险恶的地主,~~我们当然会让结束时间为 l a s t 1 last1 last1​​​ 的录音机来录制,说白了就是 少让它休息(这样弄久了录音机会坏的吧)。

l a s t 1 > s i last1>s_i last1>si l a s t 2 ≤ s i last2\le s_i last2si,那就只能用结束时间为 l a s t 2 last2 last2 的录音机来录制了。

Code \text{Code} 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 n n 头编号为 1 ∼ n 1 \sim n 1n 的奶牛,一开始奶牛 i i i 位于位置 p i p_i pi p 1 ∼ p n p_1\sim p_n p1pn 1 ∼ n 1\sim n 1n 的一个排列)。有 m m m 个编号为 1 ∼ m 1\sim m 1m 的虫洞,虫洞 i i i 双向连接了位置 u i u_i ui v i v_i vi,宽度为 w i w_i wi。两头位于一个虫洞两端的奶牛可以选择通过虫洞交换位置。奶牛们需要反复进行这样的交换,直到奶牛 i i i 位于位置 i i i​。求用来排序的虫洞宽度的最小值的最大值是多少。保证奶牛们能排好序。如果奶牛们不需要用任何虫洞来排序,输出 − 1 -1 1

思路

看到

最小值的最大值

立马想到二分(已经快成条件反射了)。

二分下标和答案应该都可以,我用了二分答案。

1 ≤ w i ≤ 1 0 9 1\le w_i\le10^9 1wi109,所以​取 l = 1 , r = 1 0 9 , m i d = l + r + 1 2 l=1,r=10^9,mid=\frac{l+r+1}{2} l=1,r=109,mid=2l+r+1,每次 check ⁡ ( m i d ) \operatorname{check}(mid) check(mid) 将所有 w i ≥ m i d w_i\ge mid wimid 都在 u i u_i ui v i v_i vi 间连一条边。

然后判断每个 i i i​​​ 和 p i p_i pi​​​ 是否在一个连通块内,若在,则说明从 i i i​​​ 出发走若干条边能到达 p i p_i pi​​​,即从 i i i​​​ 开始交换若干次能交换到 p i p_i pi(请各位感性理解一下)。

然后就没有然后了。

~~然后,~~记得判 − 1 -1 1

Code \text{Code} 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 n n 个点 m m m​ 条边的无向图,回答若把所有与点 u u u 连接的边(不包括点 u u u)去掉后图中会有多少个 有序 点对 < x , y > <x,y> <x,y> 不能互相到达。保证给出的图连通。

思路

割点 + + + 树形 d p \rm dp dp

考虑 u u u

  1. u u u 不是割点:那么只有 u u u​ 与剩下的点不能互相到达,注意是有序点对,所以有 2 ( n − 1 ) 2(n-1) 2(n1) 组。
  2. u u u 是割点:此时图分成了若干个连通块,主要可以分成以下 3 3 3 类:
    1. u u u​​ 的每个满足 d f n u ≤ l o w v dfn_u\le low_v dfnulowv​ 的儿子以及各自的子树:设共有 k k k​ 个满足条件的儿子,分别为 s o n 1 ∼ s o n k son_1\sim son_k son1sonk​,节点 x x x​ 的子树内共有 s i z x siz_x sizx​ 个节点。则儿子 s o n i son_i soni​ 对答案的贡献为 s i z s o n i × ( n − s i z s o n i ) siz_{son_i}\times(n-siz_{son_i}) sizsoni×(nsizsoni)​​。​
    2. u u u:他对于答案的贡献为 ( n − 1 ) (n-1) (n1)
    3. 剩下的部分:对答案的贡献为 ( n − 1 − ∑ i = 1 k s i z s o n i ) × ( 1 + ∑ i = 1 k s i z s o n i ) (n-1-\sum\limits_{i=1}^k siz_{son_i})\times(1+\sum\limits_{i=1}^k siz_{son_i}) (n1i=1ksizsoni)×(1+i=1ksizsoni)
      综上,答案为
      ∑ i = 1 k s i z s o n i × ( n − s i z s o n i ) + ( n − 1 ) + ( n − 1 − ∑ i = 1 k s i z s o n i ) × ( 1 + ∑ i = 1 k s i z s o n 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})} i=1ksizsoni×(nsizsoni)+(n1)+(n1i=1ksizsoni)×(1+i=1ksizsoni)​。
Code \text{Code} 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{完结撒花!}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值