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
前置題目
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
題意
給定一張有 \(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{完結撒花!}\)