SCOI 2015 Day1 简要题解

「SCOI2015」小凸玩矩阵

题意

一个 \(N \times M\)( $ N \leq M $ )的矩阵 $ A $,要求小凸从其中选出 $ N $ 个数,其中任意两个数字不能在同一行或同一列,现小凸想知道选出来的 $ N $ 个数中第 $ K $ 大的数字的最小值是多少。

$ 1 \leq K \leq N \leq M \leq 250, 1 \leq A_{i, j} \leq 10 ^ 9 $

题解

一道简单的网络流题。

不难发现第 \(K\) 大和第 \(N - K + 1\) 小是本质一样的。

所以就是要使得第 \(N - K + 1\) 小尽量小,那么我们二分这个最小值 \(min\) 就行了。

然后我们对于 \(A_{i, j} \le min\) 的点 \((i, j)\) 考虑,如果能从中选出至少 \(N - K + 1\) 个点就是合法的。

然后直接上二分图建模就行了。

Hungray 可以跑过去 ,但是更喜欢 Dinic 的复杂度。

所以最后复杂度就是 \(O(N^2 \log A)\) 的,轻松通过,甚至能跑更大范围。

代码

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
	freopen ("2006.in", "r", stdin);
	freopen ("2006.out", "w", stdout);
#endif
}

const int N = 255, inf = 0x3f3f3f3f;

int n, m, k, a[N][N];

template<int Maxn, int Maxm>
struct Dinic {

	int Head[Maxn], Next[Maxm], to[Maxm], cap[Maxm], e;

	void Init() {
		Set(Head, 0); e = 1;
	}

	inline void add_edge(int u, int v, int flow) {
		to[++ e] = v; Next[e] = Head[u]; cap[e] = flow; Head[u] = e;
	}

	inline void Add(int u, int v, int flow) {
		add_edge(u, v, flow); add_edge(v, u, 0);
	}

	int S, T, dis[Maxn];

	bool Bfs() {
		queue<int> Q; Q.push(S); Set(dis, 0); dis[S] = 1;
		while (!Q.empty()) {
			int u = Q.front(); Q.pop();
			for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]])
				if (cap[i] && !dis[v]) dis[v] = dis[u] + 1, Q.push(v);
		}
		return dis[T];
	}

	int cur[Maxn];
	int Dfs(int u, int flow) {
		if (!flow || u == T) return flow;
		int res = 0, f;
		for (int& i = cur[u], v = to[i]; i; v = to[i = Next[i]])
			if (dis[v] == dis[u] + 1 && (f = Dfs(v, min(flow, cap[i])))) {
				res += f; cap[i] -= f; cap[i ^ 1] += f; 
				if (!(flow -= f)) break ;
			}
		return res;
	}

	inline int Run() {
		int res = 0;
		while (Bfs())
			Cpy(cur, Head), res += Dfs(S, inf);
		return res;
	}

};

Dinic<N * 2, N * N * 2> T;

bool Check(int lim) {
	T.Init();
	T.S = n + m + 1, T.T = n + m + 2;
	For (i, 1, n) For (j, 1, m)
		if (a[i][j] <= lim) T.Add(i, j + n, 1);
	For (i, 1, n) T.Add(T.S, i, 1);
	For (i, 1, m) T.Add(i + n, T.T, 1);
	return T.Run() >= k;
}

int main () {

	File();

	n = read(); m = read(); k = n - read() + 1;

	int l = inf, r = -inf;
	For (i, 1, n) For (j, 1, m)
		a[i][j] = read(), chkmin(l, a[i][j]), chkmax(r, a[i][j]);

	int ans = 0;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (Check(mid)) ans = mid, r = mid - 1;
		else l = mid + 1;
	}
	printf ("%d\n", ans);

	return 0;

}

「SCOI2015」国旗计划

题意

给你一个长为 \(M\) 的环,共有 \(N\) 个互不包含的环上区间 \([C_i, D_i]\) (当 \(C_i > D_i\) 的时候相当于 \([C_i, M]\)\([1, D_i]\) 拼成的一个区间)。

每次强制选择第 \(i\) 个区间,询问至少还要选择多少个区间才能满足所有点都被覆盖。

$ N \leq 2 \times 10 ^ 5, M < 10 ^ 9, 1 \leq C_i, D_i \leq M $

题解

如果是序列上,那么就是很简单的一道倍增题了。

只需要处理一下每个点被跨过的区间 \([l_i, r_i]\) 的最大的 \(r_i\) 就行了,因为每次都向尽量走的远。

然后预处理第 \(i\) 号点走 \(2^j\) 步能到达的最远点 \(to_{i, j}\) 就行了。每次查询直接倍增就行了。

环上的麻烦一点,首先倍长拆环。然后你有可能你会绕环走了好几圈,这个你就多记下一个走了 \(2^j\) 步走了的距离 \(dis_{i, j}\) 就行了。(因为这样比较好写)

每次要走的距离分类讨论就行了。

然后最后的复杂度就是 \(O(N \log N)\) 的,轻松通过。

其实可以把这个倍增结构放到树上,每次就只需要查 \(dis\) 的差就行了(如果不在子树内要 \(+1\) ),复杂度可以优化成 \(O(N)\) 的。

代码

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#define pb push_back

using namespace std;

typedef long long ll;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
	freopen ("2007.in", "r", stdin);
	freopen ("2007.out", "w", stdout);
#endif
}

const int N = 4e5 + 1e3;

ll dis[N * 2][21]; 
int to[N * 2][21], l[N], r[N];

vector<int> V[N * 2];

int n, m, Hash[N * 2], len;

inline int Get_Id(int x) {
	return lower_bound(Hash + 1, Hash + len + 1, x) - Hash;
}

int main () {

	File();

	n = read(); m = read();

	For (i, 1, n) {
		Hash[++ len] = l[i] = read();
		Hash[++ len] = r[i] = read();
	}
	sort(Hash + 1, Hash + len + 1);
	len = unique(Hash + 1, Hash + len + 1) - Hash - 1;

	For (i, 1, n) {
		l[i] = Get_Id(l[i]), r[i] = Get_Id(r[i]);
		if (l[i] > r[i]) V[1].pb(r[i]), V[l[i]].pb(r[i] + len); 
		else V[l[i]].pb(r[i]);
	}

	int cur = 0;
	For (i, 1, len) {
		for (int v : V[i]) chkmax(cur, v);
		dis[i][0] = dis[i + len][0] = cur - i;
		to[i][0] = to[i + len][0] = cur;
	}

	int Lim = ceil(log2(len << 1));

	For (j, 1, Lim) For (i, 1, len << 1) {
		to[i][j] = to[to[i][j - 1]][j - 1];
		dis[i][j] = dis[i][j - 1] + dis[to[i][j - 1]][j - 1];
	}

	For (i, 1, n) {
		int ans = 0, u = r[i], need = l[i] <= r[i] ? len - (r[i] - l[i]) : l[i] - r[i];
		Fordown (j, Lim, 0)
			if (dis[u][j] < need)
				need -= dis[u][j], u = to[u][j], ans |= 1 << j;
		printf ("%d%c", ans + 2, i == iend ? '\n' : ' ');
	}

	return 0;

}
posted @ 2018-11-25 20:37  zjp_shadow  阅读(260)  评论(0编辑  收藏  举报