『模拟赛题解』10.1 NOIP 模拟赛
10.1 模拟赛
T1. 反转 Dag 图
Description
给出一个 \(n\) 个点 \(m\) 条边的有向图,顶点编号 \(1\) 到 \(n\),边的编号为 \(1\) 到 \(m\),第 \(i\) 条边从点 \(u_i\) 到点 \(v_i\) 。
你可以选择一些边进行反转(从 \(u_i\) 到 \(v_i\) 的边反转后变为从 \(v_i\) 到 \(u_i\) 的边),第 \(i\) 条边反转的代价为 \(c_i\)。最终使得整个图变成一个 DAG 图。
总的反转代价是由所有需要反转的边中,代价最高的那一条决定的,求达成目标的最小代价。
Solution
二分边权 \(K\),判断是否为 DAG 图。
如果边权大于 \(K\) 的边所组成的图中有环,则只修改边权小于 \(K\) 的边无法达到目的。
如果边权大于 \(K\) 的边所组成的图中无环,则一定存在对应的方案,使得修改过的图是无环的。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int n, m, cnt, cnt2, maxc;
int head[maxn], head2[maxn], d[maxn];
struct Edge
{
int to, x, nxt, w;
} edge[maxn];
struct Edge2
{
int to, nxt;
} edge2[maxn];
void addedge(int x, int y, int w)
{
edge[++ cnt].to = y;
edge[cnt].x = x;
edge[cnt].w = w;
edge[cnt].nxt = head[x];
head[x] = cnt;
}
void addedge2(int x, int y)
{
edge2[++ cnt2].to = y;
edge2[cnt2].nxt = head2[x];
head2[x] = cnt2;
}
bool toposort(int t)
{
// cout << t << " :Toposort" << endl;
queue <int> q;
for (int i = 1; i <= t; i ++)
if (d[i] == 0)
q.push(i);
int cnt = 0;
while (!q.empty())
{
int x = q.front();
q.pop();
// cout << x << " :Toposort x" << endl;
for (int i = head2[x]; i; i = edge2[i].nxt)
{
int y = edge2[i].to;
// cout << y << " :Toposort y" << endl;
d[y] --;
if (d[y] == 0)
q.push(y);
// cout << d[y] << " :Toposort d[y]" << endl;
}
cnt ++;
}
if (cnt != n)
return false;
return true;
}
bool check(int mid)
{
memset(head2, 0, sizeof(head2));
memset(d, 0, sizeof(d));
for (int i = 1; i <= cnt2; i ++)
edge2[i].nxt = edge2[i].to = 0;
cnt2 = 0;
int sz = 0;
for (int i = 1; i <= cnt; i ++)
{
if (edge[i].w >= mid)
{
// cout << edge[i].x << " " << edge[i].to << endl;
addedge2(edge[i].x, edge[i].to);
// cout << edge[i].to << " " << d[edge[i].to] << endl;
d[edge[i].to] ++;
}
}
if (toposort(n))
return false;
else
return true;
}
int main()
{
freopen("antidag.in", "r", stdin);
freopen("antidag.out", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++)
{
int x, y, c;
scanf("%d%d%d", &x, &y, &c);
addedge(x, y, c);
maxc = max(maxc, c);
}
int l = 1, r = maxc, ans;
while (l < r)
{
// cout << l << " " << r << endl;
int mid = (l + r) >> 1;
// cout << mid << endl;
if (check(mid))
l = mid + 1, ans = mid;
else
r = mid;
}
cout << ans;
// cout << check(1) << endl;
// cout << check(1);
return 0;
}
T2. 歪脖子树
Description
杭州有一棵 \(n\) 个点的歪脖子树(节点编号 \(1 \sim n\)),每个点有一个丑陋度 \(a_i\),它的长相过于奇怪,以至于人们甚至不知道它的根在哪里。
为此,植物学家们做了 \(Q\) 次实验,每次实验形如下列三者之一:
- \(V \; x \; y\) :把点 \(x\) 的丑陋度改成 \(y\);
- \(E \; x\) :尝试将 \(x\) 作为树根;
- \(Q \; x\) :查询 \(x\) 的子树中丑陋度的最小值;
Solution
对于操作 3 而言,查询的子树无非 \(2\) 种可能,即最开始的 \(x\) 的子树 \(T_x\),或 \(T_x - x\) 的补集。
它们分别对应 dfs
序的一个区间,或一个前缀跟后缀的并。
线段树维护区间最小值即可。
T3. 倒水问题
Description
有 \(n\) 个水杯,水杯中分别有 \(a_i\) 升水,你采用以下方式把所有水杯倒空。
设当前所有水杯中水量最多的一杯有 \(G\) 升,你可以把所有水量在 \(\lfloor \! \frac{G}{2} \! \rfloor\) 以上的水杯都倒空。
然后继续这个操作,直到所有水杯被倒空为止。设总共倒了 \(H\) 次。
不过在干这个事情之前,你可以先选出 \(K\) 个水杯,预先将其中的水都倒空。你可以通过不同的选择,让 最终的 \(H\) 尽可能小。问最小的 \(H\) 是多少,同时输出预先倒空的水杯数量(有可能小于 \(K\))?
Solution
首先倒水次数是 \(O(\log n)\) 级别的。我们先对所有水杯按照水量排序。
然后考虑 DP
,\(f_{i, j}\) 表示将前 \(i\) 杯水用 \(j\) 次倒空,所需要提前移除的最少的水杯数量。
-
\(f_{i,j}\) 从 \(f_{i-1,j}\) 转移的话,则需要提前多移除一个水杯。
-
\(f_{i,j}\) 从 \(f_{K,j-1}\) 转移的话,则第 \(K\) 杯水的水量小于第 \(i\) 杯水的一半,且第 \(K + 1\) 杯水的水量大于等于第 \(i\) 杯的一半。 也就是说我们多花费一次倒水的机会,将 \(k + 1\) 到 \(i\) 的水杯全部倒空。
因此完整的状态转移是:
最终我们找出 \(f_{n, i} \le k\) 的最大的 \(i\) ,时间复杂度为 \(O (n \log (a_i))\)。
Code
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int n, k;
int a[maxn], pos[maxn], f[maxn][32];
int main()
{
freopen("water.in", "r", stdin);
freopen("water.out", "w", stdout);
cin >> n >> k;
for (int i = 1; i <= n; i ++)
cin >> a[i];
sort(a + 1, a + n + 1);
// cout << n << " " << k << endl;
for (int i = 0; i <= n; i ++)
for (int j = 0; j <= 31; j ++)
f[i][j] = 0x3f3f3f3f;
for (int i = 0; i <= 31; i ++)
f[0][i] = 0;
for (int i = 1; i <= k; i ++)
f[i][0] = i;
for (int i = 1; i <= n; i ++)
pos[i] = (upper_bound(a + 1, a + n + 1, a[i] / 2) - a);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= 31; j ++)
f[i][j] = min(f[i - 1][j] + 1,f[pos[i] - 1][j - 1]);
// for (int i = 1; i <= n; i ++)
// for (int j = 1; j <= 31; j ++)
// cout << f[i][j] << " ";
for (int i = 0; i <= 31; i ++)
if (f[n][i] <= k)
{
cout << i << " " << f[n][i] << endl;
return 0;
}
return 0;
}
T4. 树的颜色
略。