Solution Set【2024.1.29】
A. 开心消消乐
首先考虑给定字符串如何判定是否合法。设 \(f_{i, 0 / 1, 0 / 1}\) 表示在前缀 \(i\) 位后添加一个 \(0 / 1\) 是否可以使得其变为 \(0 / 1\),\(f\) 有意义当且仅当 \(i\) 为偶数。
考虑如何转移,即从 \(f_{i - 2, 0 / 1, 0 / 1}\) 转移到 \(f_{i, 0 / 1, 0 / 1}\)。
记 \(x = s_{i - 1}, y = s_{i}\) 考虑枚举要添加的值 \(z\) 和要转化的值 \(t\),那么我们要转移到的状态记为 \(f_{i, z, t}\)。
那么我们在转移的过程中按选取的前缀顺序考虑如下两种情况:
- 选取前缀 \(i - 1\),即根据 \(f_{i - 2, x, 0 / 1}\) 的值加上 \(y, z\) 的值考虑其是否可以消为 \(t\)。
- 选取前缀 \(i + 1\),即先将 \(x, y, z\) 合并,之后与 \(f_{i - 2, x, 0 / 1}\) 合并得到结果。
处理出 \(f_{n - 1, 0 / 1, 0 / 1}\) 的值后将 \(s_n\) 与之合并即可得到答案。
进而我们得到了一个 \(\mathcal{O}(n)\) 的 DP,称之为判定 DP。下面考虑如何计数。
重新描述一下我们要求的东西:满足可以消为 \(1\) 的 01 串数量。如果用判定 DP 刻画的化可以转化为:前 \(n - 1\) 个字符的判定 DP 值可以和最后一个字符合并得到 1 的字符串数量。
发现判定 DP 的值只有 \(2^4 = 16\) 种可能,考虑将其作为 DP 状态进行额外的一层 DP,预处理转移边即可。
复杂度为 \(\mathcal{O}(n)\),常数不小。
Code
#include <bits/stdc++.h>
typedef int valueType;
typedef std::vector<valueType> ValueVector;
typedef std::array<std::array<bool, 2>, 2> BitSet;
typedef std::vector<BitSet> BitMatrix;
namespace MODINT_WITH_FIXED_MOD {
constexpr valueType MOD = 998244353;
template<typename T1, typename T2>
void Inc(T1 &a, T2 b) {
a = a + b;
if (a >= MOD)
a -= MOD;
}
template<typename T1, typename T2>
void Dec(T1 &a, T2 b) {
a = a - b;
if (a < 0)
a += MOD;
}
template<typename T1, typename T2>
T1 sum(T1 a, T2 b) {
return a + b >= MOD ? a + b - MOD : a + b;
}
template<typename T1, typename T2>
T1 sub(T1 a, T2 b) {
return a - b < 0 ? a - b + MOD : a - b;
}
template<typename T1, typename T2>
T1 mul(T1 a, T2 b) {
return (long long) a * b % MOD;
}
template<typename T1, typename T2>
void Mul(T1 &a, T2 b) {
a = (long long) a * b % MOD;
}
template<typename T1, typename T2>
T1 pow(T1 a, T2 b) {
T1 result = 1;
while (b > 0) {
if (b & 1)
Mul(result, a);
Mul(a, a);
b = b >> 1;
}
return result;
}
} // namespace MODINT_WITH_FIXED_MOD
using namespace MODINT_WITH_FIXED_MOD;
void solve() {
std::string T_, S_;
std::cin >> T_ >> S_;
std::swap(T_[1], T_[4]);
std::swap(T_[3], T_[6]);
valueType const N = S_.length(), M = N / 2;
if (N == 1) {
if (S_[0] == '1' || S_[0] == '?')
std::cout << 1 << std::endl;
else
std::cout << 0 << std::endl;
return;
}
ValueVector T(T_.begin(), T_.end()), S(S_.begin(), S_.end());
for (auto &c : T)
c -= 48;
for (auto &c : S)
c -= 48;
std::array<std::array<valueType, 4>, 1 << 4> Transfer;
for (valueType s = 0; s < (1 << 4); ++s) {
BitSet prev;
prev[0][0] = (s >> 0) & 1;
prev[0][1] = (s >> 1) & 1;
prev[1][0] = (s >> 2) & 1;
prev[1][1] = (s >> 3) & 1;
for (valueType x = 0; x < 2; ++x) {
for (valueType y = 0; y < 2; ++y) {
BitSet next;
next[0][0] = next[0][1] = next[1][0] = next[1][1] = false;
for (valueType k = 0; k < 2; ++k) {
next[k][0] = next[k][0] || prev[T[x << 2 | y << 1 | k]][0];
next[k][1] = next[k][1] || prev[T[x << 2 | y << 1 | k]][1];
next[k][T[0 << 2 | y << 1 | k]] = next[k][T[0 << 2 | y << 1 | k]] || prev[x][0];
next[k][T[1 << 2 | y << 1 | k]] = next[k][T[1 << 2 | y << 1 | k]] || prev[x][1];
}
valueType t = 0;
if (next[0][0])
t |= 1 << 0;
if (next[0][1])
t |= 1 << 1;
if (next[1][0])
t |= 1 << 2;
if (next[1][1])
t |= 1 << 3;
Transfer[s][x << 1 | y] = t;
}
}
}
valueType const free = '?' - 48;
std::vector<std::array<valueType, 1 << 4>> F(M);
for (auto &a : F)
a.fill(0);
valueType const start = 1 << 0 | 1 << 3;
for (valueType x = 0; x < 2; ++x) {
for (valueType y = 0; y < 2; ++y) {
if (S[0] != free && x != S[0])
continue;
if (S[1] != free && y != S[1])
continue;
Inc(F[0][Transfer[start][x << 1 | y]], 1);
}
}
for (valueType i = 1; i < M; ++i) {
for (valueType prev = 0; prev < (1 << 4); ++prev) {
for (valueType x = 0; x < 2; ++x) {
for (valueType y = 0; y < 2; ++y) {
if (S[i << 1] != free && x != S[i << 1])
continue;
if (S[i << 1 | 1] != free && y != S[i << 1 | 1])
continue;
Inc(F[i][Transfer[prev][x << 1 | y]], F[i - 1][prev]);
}
}
}
}
valueType ans = 0;
for (valueType s = 0; s < (1 << 4); ++s) {
BitSet prev;
prev[0][0] = (s >> 0) & 1;
prev[0][1] = (s >> 1) & 1;
prev[1][0] = (s >> 2) & 1;
prev[1][1] = (s >> 3) & 1;
for (valueType x = 0; x < 2; ++x) {
if (S[N - 1] != free && x != S[N - 1])
continue;
if (prev[x][1])
Inc(ans, F[M - 1][s]);
}
}
std::cout << ans << std::endl;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
#endif
valueType T;
std::cin >> T;
for (valueType test = 1; test <= T; ++test)
solve();
}
B. 树上的棋局
首先考察其 \(\operatorname{SG}\) 函数值。
发现对于叶子 \(x\),有 \(\operatorname{SG}\left(x\right) = 0\)。而对于其他节点,其可以到达的状态为其子树内除去其自身以外的全部节点。因此通过归纳我们可以得到每个节点 \(u\) 的 \(\operatorname{SG}\) 函数值均为其子树内最远的叶子与其的距离,即其为 \(f_u\)。
因此在初始局面时,该游戏的 \(\operatorname{SG}\) 值为所有节点 \(\operatorname{SG}\) 值的异或和,即 \(\bigoplus\limits_{x}\operatorname{SG}\left(x\right)\)。
同时可以发现,若一个节点产生偶数个叶子,那么其对整个游戏的 \(\operatorname{SG}\) 函数值贡献为 \(0\),否则为 \(f_u\)。这样我们就可以通过线段树维护树链剖分处理对于某些节点加棋子的操作。
接下来考虑如何换根。发现根实质上是通过限制叶子的选择必须在子树内而改变了 \(f_u\) 的值。那么我们先不考虑子树内的限制,设 \(g_u\) 表示整棵树中距离 \(u\) 最远的叶子的距离。发现若一个节点能产生 \(g_u\) 的贡献当且仅当距离其最远的叶子节点,\(u\) 和根节点依次在一条链上。其中距离其最远的叶子节点一定是树的直径的某一端点,具体的,我们求出树的直径,可以发现对于直径中点一侧的节点距离其最远的叶子节点均为另一侧的直径端点。因此在设定根后,可以产生 \(g_u\) 贡献的节点为其到直径中点的链上的节点,其余节点均只能产生距离其次大的叶子的贡献。
至此我们已经可以维护上述信息了。对于每个节点定义其共有四种状态:
- 产生次大的叶子的贡献
- 可以产生次大的叶子的贡献但是有偶数个棋子
- 产生最远的叶子的贡献
- 可以产生最远的叶子的贡献但是有偶数个棋子
不难发现各种操作实际上是对这些状态的切换,使用线段树维护即可。
复杂度为 \(\mathcal{O}(n \log^2 n)\)。
Code
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
#include <bits/stdc++.h>
typedef int valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::vector<bool> bitset;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::vector<ValuePair> PairVector;
typedef std::vector<PairVector> PairMatrix;
typedef std::array<valueType, 4> FourArray;
typedef std::vector<FourArray> ArrayVector;
class Tree {
private:
valueType N;
ArrayVector Data; // 00 / 01 : 选择子树内或被钦定情况下 产生 / 不产生 贡献的贡献异或和
// 10 / 11 : 不选择重儿子子树内叶子情况下 产生 / 不产生 贡献的贡献异或和
// 00 状态的值为答案
ValueVector LazyTag;
void PushUp(valueType id) {
Data[id][0] = Data[id << 1][0] ^ Data[id << 1 | 1][0];
Data[id][1] = Data[id << 1][1] ^ Data[id << 1 | 1][1];
Data[id][2] = Data[id << 1][2] ^ Data[id << 1 | 1][2];
Data[id][3] = Data[id << 1][3] ^ Data[id << 1 | 1][3];
}
void ApplyTag(valueType id, valueType tag) {
FourArray const prev = Data[id];
Data[id][0] = prev[0 ^ tag];
Data[id][1] = prev[1 ^ tag];
Data[id][2] = prev[2 ^ tag];
Data[id][3] = prev[3 ^ tag];
}
void Push(valueType id) {
if (LazyTag[id] != 0) {
ApplyTag(id << 1, LazyTag[id]);
ApplyTag(id << 1 | 1, LazyTag[id]);
LazyTag[id << 1] ^= LazyTag[id];
LazyTag[id << 1 | 1] ^= LazyTag[id];
LazyTag[id] = 0;
}
}
public:
Tree() = default;
explicit Tree(valueType n, ValueVector const &sourceA, ValueVector const &sourceB) : N(n), Data(4 * N + 10), LazyTag(4 * N + 10, 0) {
for (auto &a : Data)
a.fill(0);
build(1, 1, N, sourceA, sourceB);
}
private:
void build(valueType id, valueType l, valueType r, ValueVector const &sourceA, ValueVector const &sourceB) {
if (l == r) {
Data[id][0] = sourceA[l];
Data[id][1] = 0;
Data[id][2] = sourceB[l];
Data[id][3] = 0;
return;
}
valueType const mid = (l + r) >> 1;
build(id << 1, l, mid, sourceA, sourceB);
build(id << 1 | 1, mid + 1, r, sourceA, sourceB);
PushUp(id);
}
public:
void Flip(valueType l, valueType r, valueType tag) {
if (l > r)
return;
Flip(1, 1, N, l, r, tag);
}
private:
void Flip(valueType id, valueType nodeL, valueType nodeR, valueType queryL, valueType queryR, valueType tag) {
if (queryL <= nodeL && nodeR <= queryR) {
ApplyTag(id, tag);
LazyTag[id] ^= tag;
return;
}
Push(id);
valueType const mid = (nodeL + nodeR) >> 1;
if (queryL <= mid)
Flip(id << 1, nodeL, mid, queryL, queryR, tag);
if (queryR > mid)
Flip(id << 1 | 1, mid + 1, nodeR, queryL, queryR, tag);
PushUp(id);
}
public:
void Assign(valueType x, valueType v, valueType tag) {
Assign(1, 1, N, x, v, tag);
}
private:
void Assign(valueType id, valueType nodeL, valueType nodeR, valueType x, valueType v, valueType tag) {
if (nodeL == nodeR) {
Data[id][tag ^ LazyTag[id]] = v;
return;
}
Push(id);
valueType const mid = (nodeL + nodeR) >> 1;
if (x <= mid)
Assign(id << 1, nodeL, mid, x, v, tag);
else
Assign(id << 1 | 1, mid + 1, nodeR, x, v, tag);
PushUp(id);
}
public:
valueType Query() const {
return Data[1][0];
}
};
valueType N;
ValueVector Weight;
PairMatrix Contribution;
ValueMatrix G;
ValueVector Father, Size, Son, Top;
ValueVector Depth, LeftBound, RightBound, Node;
Tree tree;
valueType CalcExcludeContribution(valueType x, valueType v) {
return Contribution[x][0].second == v ? Contribution[x][1].first : Contribution[x][0].first;
// valueType pointer = 0;
// while (Contribution[x][pointer].second == v)
// ++pointer;
// return Contribution[x][pointer].first;
}
bool ChooseAble(valueType x, valueType v) {
return Contribution[x][0].second == v || Contribution[x][1].second == v;
}
void dfs(valueType x, valueType from) {
Father[x] = from;
Depth[x] = Depth[from] + 1;
Size[x] = 1;
Weight[x] = 0;
Contribution[x].reserve(G[x].size() + 1);
Contribution[x].emplace_back(0, x);
for (auto const &to : G[x]) {
if (to == from)
continue;
dfs(to, x);
Size[x] += Size[to];
Weight[x] = std::max(Weight[x], Weight[to] + 1);
Contribution[x].emplace_back(Weight[to] + 1, to);
if (Son[x] == 0 || Size[to] > Size[Son[x]])
Son[x] = to;
}
std::sort(Contribution[x].begin(), Contribution[x].end(), std::greater<>());
}
void build(valueType x, valueType from, valueType top) {
if (from > 0) {
Contribution[x].emplace_back(CalcExcludeContribution(from, x) + 1, from);
std::sort(Contribution[x].begin(), Contribution[x].end(), std::greater<>());
Contribution[x].resize(2);
}
static valueType dfsCount = 0;
LeftBound[x] = ++dfsCount;
Node[dfsCount] = x;
Top[x] = top;
if (Son[x] != 0)
build(Son[x], x, top);
for (auto const &to : G[x]) {
if (to == Son[x] || to == from)
continue;
build(to, x, to);
}
RightBound[x] = dfsCount;
}
void Modify(valueType u, valueType v) {
while (Top[u] != Top[v]) {
if (Depth[Top[u]] > Depth[Top[v]])
std::swap(u, v);
tree.Flip(LeftBound[Top[v]], LeftBound[v], 1);
v = Father[Top[v]];
}
if (LeftBound[u] > LeftBound[v])
std::swap(u, v);
tree.Flip(LeftBound[u], LeftBound[v], 1);
}
valueType Jump(valueType u, valueType k) {
while (u != 0) {
if (Depth[u] - Depth[Top[u]] + 1 <= k) {
k -= Depth[u] - Depth[Top[u]] + 1;
u = Father[Top[u]];
} else {
return Node[LeftBound[u] - k];
}
}
return 0;
}
bool InTree(valueType u, valueType v) { // check if v is in the subtree of u
return LeftBound[u] <= LeftBound[v] && RightBound[v] <= RightBound[u];
}
valueType GetLCA(valueType u, valueType v) {
while (Top[u] != Top[v]) {
if (Depth[Top[u]] > Depth[Top[v]])
std::swap(u, v);
v = Father[Top[v]];
}
if (LeftBound[u] > LeftBound[v])
std::swap(u, v);
return u;
}
valueType GetDist(valueType u, valueType v) {
return Depth[u] + Depth[v] - 2 * Depth[GetLCA(u, v)];
}
valueType D1 = -1, D2 = -1;
void FlipRootPath(valueType u) {
valueType v = D1;
if (D2 != -1 && GetDist(u, D2) < GetDist(u, D1))
v = D2;
while (Top[u] != Top[v]) {
if (Depth[Top[u]] > Depth[Top[v]])
std::swap(u, v);
tree.Flip(LeftBound[Top[v]], LeftBound[v], 2);
v = Father[Top[v]];
}
if (LeftBound[u] > LeftBound[v])
std::swap(u, v);
tree.Flip(LeftBound[u], LeftBound[v], 2);
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
#endif
valueType M;
std::cin >> N >> M;
Weight.resize(N + 1, 0);
G.resize(N + 1);
Father.resize(N + 1, 0);
Size.resize(N + 1, 0);
Son.resize(N + 1, 0);
Top.resize(N + 1, 0);
Depth.resize(N + 1, 0);
LeftBound.resize(N + 1, 0);
RightBound.resize(N + 1, 0);
Node.resize(N + 1, 0);
Contribution.resize(N + 1);
for (valueType i = 1; i < N; ++i) {
valueType u, v;
std::cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, 0);
build(1, 0, 1);
{
valueType min = std::numeric_limits<valueType>::max();
for (valueType i = 1; i <= N; ++i)
min = std::min(min, Contribution[i][0].first);
for (valueType i = 1; i <= N; ++i) {
if (Contribution[i][0].first != min)
continue;
if (D1 == -1)
D1 = i;
else
D2 = i;
}
}
{
ValueVector A(N + 1, 0), B(N + 1, 0);
for (valueType i = 1; i <= N; ++i) {
A[LeftBound[i]] = Contribution[i][1].first;
B[LeftBound[i]] = Contribution[i][0].first;
}
tree = Tree(N, A, B);
}
valueType Root = 1;
FlipRootPath(Root);
for (valueType m = 0; m < M; ++m) {
valueType type;
std::cin >> type;
if (type == 1) {
valueType u, v;
std::cin >> u >> v;
Modify(u, v);
} else {
valueType x;
std::cin >> x;
if (x == Root) {
tree.Flip(1, N, 1);
} else if (InTree(x, Root)) {
valueType const diff = Depth[Root] - Depth[x];
valueType const y = Jump(Root, diff - 1);
tree.Flip(1, LeftBound[y] - 1, 1);
tree.Flip(RightBound[y] + 1, N, 1);
} else {
tree.Flip(LeftBound[x], RightBound[x], 1);
}
}
FlipRootPath(Root);
std::cin >> Root;
FlipRootPath(Root);
std::cout << tree.Query() << '\n';
}
std::cout << std::flush;
return 0;
}
C. 社会黄油飞
发现实质上我们要找到一个点集 \(V\),满足
其中 \(e(V) = \sum\limits_{\left(x, y\right) \in G} \left[x \in V \land y \in V\right]\)。不难将其转化为网络流模型:选择一条边可以获得 \(1\) 的收益,选择一个点需要支付 \(lim\) 的代价,若一条边被选择那么该边的节点也必须被选择。
但是上述模型会计算 \(V = \varnothing\) 的情况,考虑钦定一个节点必须选择,这样可以解决上述问题,但是会跑 \(\mathcal{O}(n)\) 次最大流,无法接受。考虑每次跑最大流时在上一次基础上运行,那么我们将这次钦定必须选择的节点所属的流量退掉即可。
Code
#include <bits/stdc++.h>
typedef int valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::vector<ValuePair> PairVector;
constexpr auto TimeLimit = std::chrono::milliseconds(1900);
std::mt19937 engine(std::chrono::steady_clock::now().time_since_epoch().count() ^ std::random_device()() ^ (__uint64_t) std::make_unique<char>().get());
class Dinic {
private:
struct Edge {
public:
typedef std::vector<Edge> container;
valueType to;
valueType cap;
valueType flow;
valueType pair;
Edge() : to(-1), cap(-1), flow(-1), pair(){};
Edge(valueType to, valueType cap) : to(to), cap(cap), flow(0), pair(-1){};
};
typedef Edge::container Graph;
valueType N;
Graph edges;
ValueMatrix G;
ValueVector depth;
ValueVector start;
bool Initialized;
valueType maxFlow_;
valueType S_, T_;
public:
explicit Dinic(valueType N) : N(N), G(N + 1), depth(N + 1, 0), start(N + 1), Initialized(false), maxFlow_(0), S_(1), T_(2){};
void addEdge(valueType from, valueType to, valueType cap) {
if (__builtin_expect(Initialized, false))
throw std::runtime_error("Dinic: addEdge after init");
edges.emplace_back(to, cap);
edges.emplace_back(from, 0);
G[from].emplace_back(edges.size() - 2);
G[to].emplace_back(edges.size() - 1);
edges[G[from].back()].pair = G[to].back();
edges[G[to].back()].pair = G[from].back();
}
void init() {
if (__builtin_expect(Initialized, false))
throw std::runtime_error("Dinic: init twice");
Initialized = true;
std::fill(depth.begin(), depth.end(), 0);
for (valueType i = 1; i <= N; ++i)
start[i] = 0;
}
void reset() {
if (__builtin_expect(!Initialized, false))
throw std::runtime_error("Dinic: reset before init");
for (auto &e : edges)
e.flow = 0;
std::fill(depth.begin(), depth.end(), 0);
Initialized = false;
}
valueType maxFlow() {
if (__builtin_expect(!Initialized, false))
throw std::runtime_error("Dinic: maxFlow before init");
while (bfs(S_, T_)) {
ValueVector begin = start;
maxFlow_ += dfs(S_, T_, std::numeric_limits<valueType>::max(), begin);
}
return maxFlow_;
}
void BackFlow(valueType x) {
valueType flow = 0;
for (auto const &iter : G[x]) {
auto &e = edges[iter];
if (e.to == T_) {
flow = e.flow;
e.cap = 0;
e.flow = 0;
edges[e.pair].flow = 0;
}
}
maxFlow_ -= flow;
for (auto const &iter : G[x]) {
valueType const y = edges[iter].to;
if (edges[iter].flow == 0 || flow == 0 || edges[iter].to == T_)
continue;
edges[iter].flow = 0;
for (auto const &iterY : G[y]) {
auto &e = edges[iterY];
if (e.to == S_) {
assert(e.flow == -1);
--flow;
e.flow = 0;
edges[e.pair].flow = 0;
}
}
}
assert(flow == 0);
}
void ReBackFlow(valueType x, valueType f) {
for (auto const &iter : G[x]) {
auto &e = edges[iter];
if (e.to == T_)
e.cap = f;
}
}
private:
bool bfs(valueType S, valueType T) {
std::fill(depth.begin(), depth.end(), 0);
std::queue<valueType> Q;
Q.push(S);
depth[S] = 1;
while (!Q.empty()) {
valueType const u = Q.front();
Q.pop();
for (auto const &iter : G[u]) {
auto const &e = edges[iter];
if ((e.cap > e.flow) && (!depth[e.to])) {
depth[e.to] = depth[u] + 1;
Q.push(e.to);
}
}
}
return depth[T] > 0;
}
valueType dfs(valueType u, valueType T, valueType flow, ValueVector &Begin) {
if (u == T || flow == 0)
return flow;
valueType result = 0;
for (auto &iter = Begin[u]; iter < G[u].size(); ++iter) {
auto &e = edges[G[u][iter]];
if (e.cap > e.flow && depth[e.to] == depth[u] + 1) {
valueType const f = dfs(e.to, T, std::min(flow - result, e.cap - e.flow), Begin);
e.flow += f;
edges[e.pair].flow -= f;
result += f;
if (result == flow)
return flow;
}
}
return result;
}
};
constexpr valueType INF = std::numeric_limits<valueType>::max();
int main() {
auto const __begin = std::chrono::steady_clock::now();
auto CheckTime = [&]() {
auto __end = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(__end - __begin) < TimeLimit;
};
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("socialbutterfly.in", "r", stdin);
freopen("socialbutterfly.out", "w", stdout);
#endif
valueType N, M, Limit;
std::cin >> N >> M >> Limit;
PairVector edges(M + 1);
for (valueType i = 1; i <= M; ++i)
std::cin >> edges[i].first >> edges[i].second;
valueType size = 0;
valueType const S = ++size, T = ++size;
ValueVector NodeId(N + 1, 0), EdgeID(M + 1, 0);
for (valueType i = 1; i <= N; ++i)
NodeId[i] = ++size;
for (valueType i = 1; i <= M; ++i)
EdgeID[i] = ++size;
Dinic dinic(size);
for (valueType i = 1; i <= N; ++i)
dinic.addEdge(NodeId[i], T, Limit);
for (valueType i = 1; i <= M; ++i) {
dinic.addEdge(S, EdgeID[i], 1);
auto const &[u, v] = edges[i];
dinic.addEdge(EdgeID[i], NodeId[u], INF);
dinic.addEdge(EdgeID[i], NodeId[v], INF);
}
dinic.init();
for (valueType x = 1; x <= N; ++x) {
dinic.BackFlow(NodeId[x]);
if (M - dinic.maxFlow() > 0) {
std::cout << "Yes" << std::endl;
return 0;
}
dinic.ReBackFlow(NodeId[x], Limit);
}
std::cout << "No" << std::endl;
return 0;
}