2021.8.10 比赛题整理
UPDATE 21/08/26
更改题面、转移链接等。
2021.8.10 新初一暑假测试五
11 / 18,190 / 400。
T1
签到题,就不说了。简单的一批(题目说行末不能有空格,结果加了也 ac,就离谱)。
T2 Recording the Moolympics
P2255 [USACO14JAN]Recording the Moolympics S
在书上看过一模一样的题目和讲解,结果还是错了…
因为是问可以录制的最大数量的节目,不是 dp 就是贪心 (当然也能暴力),但前面的选择对后面不会造成太大影响,所以用贪心。
-
我们根据每个时间段的结束时间的早晚进行排序。是结束时间!!不是开始时间!
贪心是在尽量少的时间里拍尽量多的节目,所以要最小化每次结束的时间,以让后面能拍更多节目。
-
这道题还有两个难点是有两台摄像机 + 代码实现(做到让代码越简单越好)。
我们在遍历排好序的时间段时, 用两个变量记录每次的结束时间,两个变量,我们要保证一个永远要比另一个大(swap 即可)。
因为我们对两个变量的处理顺序是一定的,所以每次要让新时间段的开始时间和上一个的结束时间之间的差越小越好。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 205;
int n;
int maxf, maxs;
struct node{
int s, t;
int vis;
}e[maxn * 2];
int f;
int read ()
{
int x = 1, s = 0;
char ch = getchar ();
while (ch < '0' or ch > '9'){if (ch == '-') x = -1; ch = getchar ();}
while (ch >= '0' and ch <= '9'){s = s * 10 + ch - '0'; ch = getchar ();}
return x * s;
}
bool cmp (node a, node b)
{
if (a.t != b.t)
return a.t < b.t;
return a.s < b.s;
}
signed main ()
{
n = read ();
for (int i = 1; i <= n; i++)
{
e[i].s = read ();
e[i].t = read ();
e[i].vis = 0;
}
sort (e + 1, e + n + 1, cmp);
int ans = 0, r1 = 0, r2 = 0;
for (int i = 1; i <= n; i++)
{
if (r1 <= e[i].s)
{
ans++;
r1 = e[i].t;
}
else if (r2 <= e[i].s)
{
ans++;
r2 = e[i].t;
}
if (r1 < r2) swap (r1, r2);
}
printf ("%lld", ans);
return 0;
}
T3 Wormhole Sort
P6004 [USACO20JAN] Wormhole Sort S
思路一
啊这道题…我的整体思路 (还花了我二三十分钟想) 是完全符合题解的:
按每条边的边权从小到大进行排序,每添加一条边(让这条边能走),然后遍历每一个点,如果都能走回原位,就输出最近一次添加的边的边权。
我用了前向星直接简单粗暴添加边,简单粗暴 dfs 遍历每一个点,然后,就 TLE 了,50 pts。
正解:用并查集。
同时,我们的代码也可以大大地精简许多许多 (wjyyy&wjy666 yyds)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, m;
const int maxn = 100005;
int a[maxn], vis[maxn], viss[maxn];
struct node{
int u, v, w;
}inpt[maxn * 2];
struct addedge{
int to, nxt;
}e[maxn * 2];
int hd[maxn], cnt;
int fa[maxn];
int check;
int read ()
{
int x = 1, s = 0;
char ch = getchar ();
while (ch < '0' or ch > '9'){if (ch == '-') x = -1; ch = getchar ();}
while (ch >= '0' and ch <= '9'){s = s * 10 + ch - '0'; ch = getchar ();}
return x * s;
}
bool cmp (node a, node b)
{
return a.w > b.w;
}
int find (int x)
{
if (fa[x] == x) return x;
return fa[x] = find (fa[x]);
}
signed main ()
{
int flag = 0;
n = read (), m = read ();
for (int i = 1; i <= n; i++)
{
a[i] = read ();
fa[i] = i;
if (a[i] < a[i - 1]) flag = 1;
}
for (int i = 1; i <= m; i++) inpt[i].u = read (), inpt[i].v = read (), inpt[i].w = read ();
if (!flag)
{
printf ("-1\n");
return 0;
}
sort (inpt + 1, inpt + m + 1, cmp);
for (int i = 1; i <= m; i++)
{
int x = find (inpt[i].u), y = find (inpt[i].v);
if (x != y) fa[y] = x;
int tmp = 1;
while (find (tmp) == find (a[tmp]))
{
tmp++;
if (tmp > n)
{
printf ("%lld\n", inpt[i].w);
return 0;
}
}
}
return 0;
}
思路二
出现“最小值的最大值”、“最大值的最小值”等,就可以断定是二分(答案)了。
这道题就是二分边权大小,看可不可行。
但是这道题明显还是思路一简单的多,二分的代码本质上也是思路一的代码啦,但是复杂得多。
代码仅作参考,代码来源 wyf 大佬(mango13)(非本人手写代码皆会标明作者或出处)。
#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
考试的时候推了半个小时 +,推出了理论思路,但是没能用代码实现出来(谁会想到 wxd 能押对题!!!挺多大佬昨天晚上打过了…)。
易得,若一个点不是割点,那么删掉它,其他点仍然连通,仍然可以互达,所以它的点数就是 2 × ( n − 1 ) 2 \times (n-1) 2×(n−1)。
但若该点 u 是一个割点,那么我们分成以下几部分算对数:
-
它的子节点分别所形成的连通块:它子节点形成的连通块的节点去连接其他所有不在该连通块的节点 s i z v × ( n − s i z v ) siz_v \times (n-siz_v) sizv×(n−sizv)。
-
节点 n 的父节点也会形成若干连通块,则 u 和 u 的子树都会和他们有对数需要记录,那我们就建一个变量 sum 去记录 u 所有子树的总节点数,所以最后的总对数就是 ( n − s u m − 1 ) × ( s u m + 1 ) (n-sum-1) \times (sum+1) (n−sum−1)×(sum+1)。
-
最后就是从 u 到其他节点,都会产生对数 n − 1 n-1 n−1。
综上,若点 u 为割点,则 u 的点数为:
a n s u = s i z 1 × ( n − s i z 1 ) + s i z 2 × ( n − s i z 2 ) + ⋯ + s i z v × ( n − s i z v ) + ( n − 1 ) + ( s u m + 1 ) × ( n − s u m − 1 ) ans_u=siz_1 \times (n-siz_1)+siz_2 \times (n-siz_2)+ \cdots +siz_v \times (n-siz_v)+(n-1)+(sum+1) \times (n-sum-1) ansu=siz1×(n−siz1)+siz2×(n−siz2)+⋯+sizv×(n−sizv)+(n−1)+(sum+1)×(n−sum−1)
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, m;
const int maxn = 500005;
int hd[maxn], cnt;
struct node{
int to, nxt;
}e[maxn * 2];
int dfn[maxn], low[maxn], siz[maxn], cut[maxn], ans[maxn];
//int sum, chil;
int tmp;
int read ()
{
int x = 1, s = 0;
char ch = getchar ();
while (ch < '0' or ch > '9'){if (ch == '-') x = -1; ch = getchar ();}
while (ch >= '0' and ch <= '9'){s = s * 10 + ch - '0'; ch = getchar ();}
return x * s;
}
int rt;
void add (int u, int v)
{
e[++cnt].to = v;
e[cnt].nxt = hd[u];
hd[u] = cnt;
}
void tarjan (int u)
{
int chil = 0;
int sum = 0;
siz[u] = 1;
dfn[u] = low[u] = ++tmp;
for (int i = hd[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (!dfn[v])
{
tarjan (v);
siz[u] += siz[v];
low[u] = min (low[u], low[v]);
if (low[v] >= dfn[u])
{
chil++;
ans[u] += (long long) siz[v] * (n - siz[v]);
sum += siz[v];
if(rt != u or chil > 1)
{
cut[u] = 1;
}
}
}
else low[u] = min (low[u], dfn[v]);
}
if (!cut[u]) ans[u] = (long long) 2 * (n - 1);
else ans[u] += (long long)(n - sum - 1) * (sum + 1) + (n - 1);
}
signed main ()
{
n = read (), m = read ();
for (int i = 1; i <= m; i++)
{
int u, v;
u = read (), v = read ();
add (u, v), add (v, u);
}
rt = 1;
tarjan (1);
for (int i = 1; i <= n; i++) printf ("%lld\n", ans[i]);
return 0;
}
—— E n d End End——
阳 和 启 蛰 , 枯 木 逢 春 。 阳和启蛰,枯木逢春。 阳和启蛰,枯木逢春。

浙公网安备 33010602011771号