小清新构造题,会就会,不会就不会。
注意到走两步很特殊,尝试从走一步拼出来,考虑归纳法:
-
随便选择一个点 \(x\),然后删掉 \(x\) 和所有 \(y\) 满足存在边 \((x, y)\)。
-
设剩下的图的答案集合为 \(S\),若不存在 \(z\in S\) 满足存在边 \((z, x)\),则将 \(x\) 加入 \(S\)。
-
否则 \(z\) 可以覆盖到 \(x\) 和所有 \(y\)(走两步覆盖两个走一步)。
时间复杂度 \(\mathcal O(n + m)\)。
- 启示:这题很困难,首先是观察这个“走两步”的特点,可以拆成两个走一步;其次是直接套用归纳法,归纳 / 增量 构造是非常常用的构造方法。
点击查看代码
#include <bits/stdc++.h>
namespace Initial {
#define ll int
#define ull unsigned long long
#define fi first
#define se second
#define mkp make_pair
#define pir pair <ll, ll>
#define pb push_back
#define i128 __int128
using namespace std;
const ll maxn = 1e6 + 10, inf = 1e9, mod = 998244353;
ll power(ll a, ll b = mod - 2, ll p = mod) {
ll s = 1;
while(b) {
if(b & 1) s = 1ll * s * a %p;
a = 1ll * a * a %p, b >>= 1;
} return s;
}
template <class T>
const inline ll pls(const T x, const T y) { return x + y >= mod? x + y - mod : x + y; }
template <class T>
const inline void add(T &x, const T y) { x = x + y >= mod? x + y - mod : x + y; }
template <class T>
const inline void chkmax(T &x, const T y) { x = x < y? y : x; }
template <class T>
const inline void chkmin(T &x, const T y) { x = x > y? y : x; }
} using namespace Initial;
namespace Read {
char buf[1 << 22], *p1, *p2;
// #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, (1 << 22) - 10, stdin), p1 == p2)? EOF : *p1++)
template <class T>
const inline void rd(T &x) {
char ch; bool neg = 0;
while(!isdigit(ch = getchar()))
if(ch == '-') neg = 1;
x = ch - '0';
while(isdigit(ch = getchar()))
x = (x << 1) + (x << 3) + ch - '0';
if(neg) x = -x;
}
} using Read::rd;
ll n, m; vector <ll> to[maxn], res;
bool vis[maxn], chs[maxn];
int main() {
rd(n), rd(m);
for(ll i = 1; i <= m; i++) {
ll u, v; rd(u), rd(v);
to[u].pb(v);
}
for(ll i = n; i; i--) {
if(vis[i]) continue;
vis[i] = chs[i] = true;
for(ll j: to[i]) vis[j] = true;
} memset(vis, false, sizeof vis);
for(ll i = 1; i <= n; i++)
if(chs[i] && !vis[i]) {
res.pb(i);
for(ll j: to[i]) vis[j] = true;
}
printf("%d\n", (ll) res.size());
for(ll x: res) printf("%d ", x); puts("");
return 0;
}