『模拟赛题解』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\) 次实验,每次实验形如下列三者之一:

  1. \(V \; x \; y\) :把点 \(x\) 的丑陋度改成 \(y\)
  2. \(E \; x\) :尝试将 \(x\) 作为树根;
  3. \(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_{i,j} = \min{f_{k, j - 1}, f_{i - 1, j} + 1} \]

最终我们找出 \(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. 树的颜色

略。

posted @ 2023-10-02 20:10  Clyfort  阅读(30)  评论(1编辑  收藏  举报