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\),需要支持以下四种操作:
- \(\operatorname{ADD}(x)\):将变量的值增加 \(x\)。
- \(\operatorname{SUB}(x)\):将变量的值减少 \(x\)。
- \(\operatorname{SET}(x)\):将变量的值变为 \(x\)。
- \(\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
题意
给定一张有 \(n\) 个点 \(m\) 条边的无向图,回答若把所有与点 \(u\) 连接的边(不包括点 \(u\))去掉后图中会有多少个 有序 点对 \(<x,y>\) 不能互相到达。保证给出的图连通。
思路
割点 \(+\) 树形 \(\rm dp\)。
考虑 \(u\):
- \(u\) 不是割点:那么只有 \(u\) 与剩下的点不能互相到达,注意是有序点对,所以有 \(2(n-1)\) 组。
- \(u\) 是割点:此时图分成了若干个连通块,主要可以分成以下 \(3\) 类:
- \(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})\)。
- \(u\):他对于答案的贡献为 \((n-1)\)。
- 剩下的部分:对答案的贡献为 \((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{完结撒花!}\)