NOI模拟(5.21) TJOID1T2 智力竞赛 (bzoj5335)

智力竞赛

题目背景:

5.21 模拟 TJOI2018D1T2

分析:floyed + 二分 + 可重点最小链覆盖

 

这道题的题意很奇葩,本质一点就是有一个有向图可以选择n + 1条链,点可以重复,问覆盖不到的权值中最小值最大是多少?

因为看原题担心会有环,还判了环,结果最后发现全部都是dag也不是很想说什么,因为是可重点,我们先用floyed跑一遍传递闭包,那么最小链覆盖就是直接进行二分图匹配,总点数 - 匹配数就是最小链覆盖了,直接二分覆盖不到的最小权值,然后判断前面的点可不可以用n + 1条链覆盖完全就可以了。复杂度O(m3logv),离散化之后logv几乎是常数,再加上匈牙利的二分图匹配也远没有到m3的上界,所以可以比较轻松的跑过。

 

Source:

 

/*
	created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>
#include <ctime>
#include <deque>
#include <iterator>
#include <map>

inline char read() {
	static const int IN_LEN = 1024 * 1024;
	static char buf[IN_LEN], *s, *t;
	if (s == t) {
		t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
		if (s == t) return -1;
	}
	return *s++;
}

// /*
template<class T>
inline void R(T &x) {
	static char c;
	static bool iosig;
	for (c = read(), iosig = false; !isdigit(c); c = read()) {
		if (c == -1) return ;
		if (c == '-') iosig = true;	
	}
	for (x = 0; isdigit(c); c = read()) 
		x = ((x << 2) + x << 1) + (c ^ '0');
	if (iosig) x = -x;
}
//*/

const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
	if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
	*oh++ = c;
}

template<class T>
inline void W(T x) {
	static int buf[30], cnt;
	if (x == 0) write_char('0');
	else {
		if (x < 0) write_char('-'), x = -x;
		for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
		while (cnt) write_char(buf[cnt--]);
	}
}

inline void flush() {
	fwrite(obuf, 1, oh - obuf, stdout);
}

const int MAXN = 500 + 10;

int n, m, k, x, cnt, top, rk[MAXN];
int match[MAXN], father[MAXN], v[MAXN], id[MAXN];
bool g[MAXN][MAXN], vis[MAXN], f[MAXN][MAXN];

std::vector<int> edge[MAXN];

struct data {
    int num, ori;
    data() {}
    data(int num, int ori) : num(num), ori(ori) {}
    inline bool operator < (const data &a) const {
        return num < a.num;
    }
} a[MAXN];

bool find(int cur) {
    for (int p = 0; p < edge[cur].size(); ++p) {
     	int v = edge[cur][p];
	    if (v && !vis[v]) {
            vis[v] = true;
            if (!match[v] || find(match[v]))
                return match[v] = cur, true;
        }
    }
    return false;
}

inline bool check(int x) {
    int cnt = 0;
    for (int i = 1; i <= n; ++i) if (v[i] < x) ++cnt, edge[i].clear();
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            if (v[i] < x && v[j] < x && f[i][j]) edge[i].push_back(j);
    for (int i = 1; i <= n; ++i) match[i] = 0;
    for (int i = 1; i <= n; ++i)
        if (v[i] < x) {
            memset(vis, false, sizeof(bool) * (n + 2));
            if (find(i)) cnt--;
        }
    return (cnt <= m + 1);
}

inline void solve() {
    R(m), R(n);
    for (int i = 1; i <= n; ++i) {
        R(v[i]), R(k);
        while (k--) R(x), g[i][x] = true;
    }
    for (register int k = 1; k <= n; ++k)
        for (register int i = 1; i <= n; ++i)
            for (register int j = 1; j <= n; ++j)
                g[i][j] |= (g[i][k] && g[k][j]);
    for (int i = 1; i <= n; ++i) father[i] = i;
    for (int i = 1; i <= n; ++i)
        for (int j = i + 1; j <= n; ++j)
            if (g[i][j] && g[j][i]) 
                father[j] = i, v[i] = std::min(v[i], v[j]);
    for (int i = 1; i <= n; ++i) 
        if (father[i] == i) id[i] = ++cnt, v[cnt] = v[i];
    for (register int i = 1; i <= n; ++i)
        for (register int j = 1; j <= n; ++j)
            if (id[i] && id[j] && id[i] != id[j]) 
                f[id[i]][id[j]] = g[i][j];
    n = cnt;
    for (int i = 1; i <= n; ++i) a[i] = data(v[i], i);
    std::sort(a + 1, a + n + 1);
    int i = 1;
    while (i <= n) {
        int j = i;
        while (i < n && a[i + 1].num == a[i].num) ++i;
        rk[++top] = a[i].num;
        for (int k = j; k <= i; ++k) v[a[k].ori] = top;
        ++i;
    }
    int l = 1, r = top + 2;
    while (l + 1 < r) {
        int mid = l + r >> 1;
        (check(mid)) ? l = mid : r = mid;
    }
    if (l <= top) std::cout << rk[l];
    else std::cout << "AK";
}

int main() {
//	clock_t start = clock();
    freopen("contest.in", "r", stdin);
    freopen("contest.out", "w", stdout);
    solve();
//    std::cerr << (double)(clock() - start) / CLOCKS_PER_SEC << '\n';
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值