1 条题解
-
0
定位为好玩的题。
简化题意:
有一张 个点 条边的无重边无自环的有向图,初始时可以选择一个点染黑,其余点均为白点
若某个点所有入边的起点均为黑点,则该点可以被染黑。最大化图中黑点数量。
首先可以观察出,若两个点的答案集合有交集,则这两个集合一定是包含关系,因此我们可以尝试将每个点的后继合并到这个点,最后输出最大的集合大小。
包含关系的证明:
暴力合并的复杂度显然是无法接受的,因此考虑如下技巧:
若该后继的某个前驱已经在当前点的答案集合,则将该前驱直接从后继的前驱集合中删去。否则说明当前点无法被合并,直接退出循环。最后将当前点插入后继的前驱集合。这样可以避免无意义查询,使得点 的遍历次数是 的。
但是合并后继时貌似有点问题:我们会将后继的后继当成新的后继,若我们构造一个链套菊花图,使得菊花图部分的点都无法被合并但链部分的点都能被合并,则菊花图部分的点会被加入链长次,这样时间复杂度就被卡到了 。
解决方案当然是有的,若我们从链顶开始做,就能避免这一情况,因此我们判断若当前点有唯一前驱且前驱编号比自己大(避免在出现环时出错),则不处理当前点,等到处理前驱时再考虑。就可以把复杂度变为 的了。
还有一个随机化做法,我当时不是很懂,现在也没理解,附在下方有需要的同学可以自己理解下。
启发式合并std:
#include <bits/stdc++.h> using namespace std; const int N = 2e5 + 10; struct dsu { int fa[N], siz[N]; inline void init(int k) { for (int i = 1; i <= k; ++i) fa[i] = i, siz[i] = 1; } int find(int k) { return k == fa[k] ? k : fa[k] = find(fa[k]); } inline bool merge(int x, int y) { x = find(x), y = find(y); if (x != y) { fa[x] = y, siz[y] += siz[x]; return true; } return false; } } d; int deg[N], n, num, t_case; set<int> fa[N], to[N]; bool vis[N]; int main() { scanf("%d", &t_case); while (t_case--) { scanf("%d", &n); for (int i = 1; i <= n; ++i) fa[i].clear(), to[i].clear(); for (int i = 1; i <= n; ++i) { scanf("%d", deg + i); for (int j = 1, x; j <= deg[i]; ++j) { scanf("%d", &x); fa[i].insert(x); to[x].insert(i); } } int ans = 0; d.init(n); for (int i = 1; i <= n; ++i) if (d.find(i) == i) { if (fa[i].size() == 1 && *fa[i].begin() > i) continue; auto update = [&](int k) { for (auto it = to[k].begin(); it != to[k].end(); ++it) if (d.find(*it) != *it) it = to[k].erase(it); }; update(i); queue<int> q; for (int j : to[i]) q.push(j); while (q.size()) { int p = q.front(); q.pop(); if (!to[i].count(p)) continue; for (auto it = fa[p].begin(); it != fa[p].end();) { if (d.find(*it) == i) it = fa[p].erase(it); else { to[i].insert(p); goto skip; } } d.merge(p, i); to[i].erase(p); update(p); for (int j : to[p]) if (i != j) q.push(j), to[i].insert(j); skip:; fa[p].insert(i); } ans = max(ans, d.siz[i]); } printf("Case #%d: %d\n", ++num, ans); } return 0; }
//Every night that summer just to seal my fate //And I scream, "For whatever it's worth //I love you, ain't that the worst thing you ever heard?" #include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #include <cctype> #include <vector> #include <cmath> #include <queue> #include <set> using namespace std; #define ll long long #define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);} #define pii pair<int,int> #define mp make_pair char buf[1 << 20], *p1, *p2; #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2)?EOF: *p1++) template <typename T> inline void read(T &t) { int v = getchar();T f = 1;t = 0; while (!isdigit(v)) {if (v == '-')f = -1;v = getchar();} while (isdigit(v)) {t = t * 10 + v - 48;v = getchar();} t *= f; } template <typename T,typename... Args> inline void read(T &t,Args&... args) { read(t);read(args...); } const int N = 2e5 + 10; int n,f[N],siz[N],cas; set <int> G1[N],G2[N]; int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); } void dfs(int u,int v) { int fx = find(u),fy = find(v); if (fx == fy) return ; if (G2[fx].size() > G2[fy].size()) swap(fx,fy); vector <pii> edg; for (auto y : G2[fx]) { G2[fy].insert(y); G1[y].erase(fx); G1[y].insert(fy); if (G1[y].size() == 1) edg.push_back(mp(fy,y)); } f[fx] = fy; siz[fy] += siz[fx]; for (auto p : edg){ dfs(p.first,p.second); } } void solve() { read(n);++cas; for (int i = 1;i <= n;++i) G1[i].clear(),G2[i].clear(),f[i] = i,siz[i] = 1; for (int i = 1;i <= n;++i) { int k;read(k); for (int j = 1;j <= k;++j) { int y;read(y); G1[i].insert(y);G2[y].insert(i); } } for (int i = 1;i <= n;++i) { if (G1[i].size() == 1) { dfs(i,*G1[i].begin()); } } int ans = 0; for (int i = 1;i <= n;++i) ans = max(ans,siz[i]); printf("Case #%d: %d\n",cas,ans); } signed main() { int T;read(T); while (T--) solve(); return 0; }
- 1
信息
- ID
- 8
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 26
- 已通过
- 1
- 上传者