• SCOI 2015 Day1 简要题解


    「SCOI2015」小凸玩矩阵

    题意

    一个 (N imes 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
    ", ans);
    
    	return 0;
    
    }
    

    「SCOI2015」国旗计划

    题意

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

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

    $ N leq 2 imes 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 ? '
    ' : ' ');
    	}
    
    	return 0;
    
    }
    
  • 相关阅读:
    小数的编程
    小数的编程
    硬币趣味题
    硬币趣味题
    好玩的 emoji
    ThreadPoolExecutor使用介绍
    希尔排序
    OpenCms创建站点过程图解——献给OpenCms的刚開始学习的人们
    Linux C 编程内存泄露检測工具(二):memwatch
    C++封装、继承、多态
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/10017112.html
Copyright © 2020-2023  润新知