UOJ #670 -【UNR #5】获奖名单(欧拉回路)

UOJ 题面传送门

首先,看到这道题要我们“构造”出一组解,我们有可能会被其带偏认为这是一道“策略构造题”,也就是 CF/AT 上许多思维题的类型。但是稍微理智地分析一下就会发现此题根本没有什么策略可言,使用贪心的思路貌似也有巨大多反例,因此我们开始不断怀疑这是一道“算法构造题”。而这题 li2 的限制又将我们往一个套路的方向思考:对于 li=2i 在对应的两点之间连边,li=1i 我们将其视作一个孤立点。这不就形成了图论的模型了吗?而与图论有关的“算法构造题”的算法无非就 2-SAT,差分约束和欧拉回路,前两个显然不可行,因此不就只剩最后一个了吗?这样我们就有了大致的方向。

因此接下来我们的任务就是明确如何“欧拉回路”,以及何是点、何是边。记 L=li。给我启发比较大的一点是先手玩一下 L 是偶数的情况,方便起见先假设最终间两个字符不属于同一个名字,那么最终间两个字符旁边的字符属于的名字的情况只可能有以下两种:

  • 两边都属于长度为 1 的名字:即 [a]|[a] 的情况。
  • 两边都属于长度为 2 的名字:即 [ab]|[ba] 的情况。
  • 一边属于长度为 1 的名字,一边属于长度为 2 的名字,不妨假设长度为 1 的名字在左边且为 a,那么右边的名字必然带 a,另一个设为 b,而左边必然有一个名字与 b 匹配,又可以分长度为 12 讨论,如果是 1 那么必须为 b,即 [b][a]|[ab],我们把这些组删掉之后似乎又归纳变为了新的 L 是偶数的情况。而如果长度是 2b 也必须跟一个字符搭配,设为 c,而根据右边 c 所在名字长度有课分类讨论,以此类推下去,最终必然有一步会出现长度为 1 的名字,即 [?!][ed][cb][a]|[ab][cd][ef][?]

如果我们建一个虚点并从所有孤立点向这个虚点连无向边,那么前两类可以视作删去一对重边,最后一类可以视作删去一个包含这个虚点的环。我们先假设存在一组解使得中间两个字符不属于同一个名字,考虑如何构造之,由于第三种情况必然包含这个虚点,因此图中与这个虚点不在同一连通块的部分必然只由一对对重边构成,它们的处理是容易的,我们可以暂时先扔了他们。而虚点所在的连通块必然是一张欧拉图,直接跑出一个欧拉回路然后将这个环一次性删除即可。

如果中间两个字符 L2,L2+1 属于同一名字,那么处理方法也是类似的:相较于前者只是多添了一个自环而已,因此我们可以直接找出这个多余的自环然后一次操作带走它即可。

这样我们就完美地解决了 L 是偶数的情况,至于 L 是奇数……其实特别 trivial 的啦。发现相当于虚点所在的连通块中多了虚点和某个其他的奇度点,直接把这个奇度点找到然后跑欧拉路径即可。

时间复杂度 O(n+m)

const int MAXN = 1e6;
int n, m, sum = 0;
vector<int> vec[MAXN + 5];
namespace EVEN {
int hd[MAXN + 5], to[MAXN * 2 + 5], nxt[MAXN * 2 + 5], ec = 1;
void adde(int u, int v) {to[++ec] = v; nxt[ec] = hd[u]; hd[u] = ec;}
int MID = 0;
vector<pii> L, R; vector<int> seq;
bool vis[MAXN + 5], used[MAXN + 5];
void dfs(int x) {
for (int &e = hd[x]; e; e = nxt[e]) if (!vis[e >> 1]) {
int tmp = e;
vis[e >> 1] = 1; dfs(to[e]); seq.pb(tmp);
}
}
vector<int> single[MAXN + 5];
map<int, vector<int> > vecs[MAXN + 5];
void solve() {
for (int i = 1; i <= n; i++) {
if (vec[i].size() == 1) adde(m + 1, vec[i][0]), adde(vec[i][0], m + 1);
else adde(vec[i][0], vec[i][1]), adde(vec[i][1], vec[i][0]);
}
dfs(m + 1);
reverse(seq.begin(), seq.end());
for (int i = 0, cur = 0, sum = 0; i < seq.size(); i++) {
int id = seq[i] >> 1;
if (!cur) L.pb(mp(id, seq[i] & 1));
else R.pb(mp(id, seq[i] & 1));
cur ^= 1;
sum ^= (vec[id].size() == 1);
if (!sum) cur = 0;
}
for (pii p : L) used[p.fi] = 1;
for (pii p : R) used[p.fi] = 1;
for (int i = 1; i <= n; i++) if (!used[i]) {
if (vec[i].size() == 2 && vec[i][0] == vec[i][1]) single[vec[i][0]].pb(i);
else vecs[min(vec[i][0], vec[i][1])][max(vec[i][0], vec[i][1])].pb(i);
}
for (int i = 1; i <= m; i++) for (auto p : vecs[i]) {
vector<int> &ids = p.se;
for (int j = 0; j + 1 < ids.size(); j += 2) {
L.pb(mp(ids[j], vec[ids[j]][1] == i));
R.pb(mp(ids[j + 1], vec[ids[j + 1]][1] == i));
}
}
for (int i = 1; i <= m; i++) {
for (int j = 0; j + 1 < single[i].size(); j += 2) {
L.pb(mp(single[i][j], 0));
R.pb(mp(single[i][j + 1], 0));
}
if (single[i].size() & 1) MID = single[i].back();
}
for (int i = (int)(L.size()) - 1; ~i; i--) printf("%d ", L[i].fi);
if (MID) printf("%d ", MID);
for (pii p : R) printf("%d ", p.fi);
printf("\n");
for (int i = (int)(L.size()) - 1; ~i; i--) printf("%d ", L[i].se ^ 1);
if (MID) printf("%d ", 0);
for (pii p : R) printf("%d ", p.se);
printf("\n");
}
}
namespace ODD {
int deg[MAXN + 5], pt;
int hd[MAXN + 5], to[MAXN * 2 + 5], nxt[MAXN * 2 + 5], ec = 1;
void adde(int u, int v) {to[++ec] = v; nxt[ec] = hd[u]; hd[u] = ec;}
vector<pii> L, R; vector<int> seq;
bool vis[MAXN + 5], used[MAXN + 5];
void dfs(int x) {
for (int &e = hd[x]; e; e = nxt[e]) if (!vis[e >> 1]) {
int tmp = e;
vis[e >> 1] = 1; dfs(to[e]); seq.pb(tmp);
}
}
vector<int> single[MAXN + 5];
map<int, vector<int> > vecs[MAXN + 5];
void solve() {
for (int i = 1; i <= n; i++) {
if (vec[i].size() == 1) adde(m + 1, vec[i][0]), adde(vec[i][0], m + 1), deg[vec[i][0]]++;
else adde(vec[i][0], vec[i][1]), adde(vec[i][1], vec[i][0]), deg[vec[i][0]]++, deg[vec[i][1]]++;
}
for (int i = 1; i <= m; i++) if (deg[i] & 1) pt = i;
dfs(pt);
reverse(seq.begin(), seq.end());
// for (int E : seq) printf("%d -> %d\n", to[E ^ 1], to[E]);
R.pb(mp(seq[0] >> 1, seq[0] & 1));
for (int i = 1, cur = 0, sum = (vec[seq[0] >> 1].size() == 1) ? 1 : 0; i < seq.size(); i++) {
int id = seq[i] >> 1;
if (!cur) L.pb(mp(id, seq[i] & 1));
else R.pb(mp(id, seq[i] & 1));
cur ^= 1;
sum ^= (vec[id].size() == 1);
if (sum) cur = 0;
}
for (pii p : L) used[p.fi] = 1;
for (pii p : R) used[p.fi] = 1;
for (int i = 1; i <= n; i++) if (!used[i]) {
if (vec[i].size() == 2 && vec[i][0] == vec[i][1]) single[vec[i][0]].pb(i);
else vecs[min(vec[i][0], vec[i][1])][max(vec[i][0], vec[i][1])].pb(i);
}
for (int i = 1; i <= m; i++) for (auto p : vecs[i]) {
vector<int> &ids = p.se;
for (int j = 0; j + 1 < ids.size(); j += 2) {
L.pb(mp(ids[j], vec[ids[j]][1] == i));
R.pb(mp(ids[j + 1], vec[ids[j + 1]][1] == i));
}
}
for (int i = 1; i <= m; i++) {
for (int j = 0; j + 1 < single[i].size(); j += 2) {
L.pb(mp(single[i][j], 0));
R.pb(mp(single[i][j + 1], 0));
}
}
for (int i = (int)(L.size()) - 1; ~i; i--) printf("%d ", L[i].fi);
for (pii p : R) printf("%d ", p.fi);
printf("\n");
for (int i = (int)(L.size()) - 1; ~i; i--) printf("%d ", L[i].se ^ 1);
for (pii p : R) printf("%d ", p.se);
printf("\n");
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1, len; i <= n; i++) {
scanf("%d", &len);
for (int j = 1, x; j <= len; j++) scanf("%d", &x), vec[i].pb(x);
sum += len;
}
if (sum % 2 == 0) EVEN :: solve();
else ODD :: solve();
return 0;
}
/*
20 20
1 9
2 9 9
2 9 9
1 19
1 9
2 9 9
2 19 19
2 9 9
1 9
2 9 9
2 9 9
2 9 19
1 9
1 9
1 9
1 9
2 19 19
2 9 9
1 19
2 9 9
*/
posted @   tzc_wk  阅读(192)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示