Loading

MX galaxy Day5

P6378 [PA 2010] Riddle

\(2-SAT\)
对每个部分内进行前后缀优化建图,每条边的端点或起来为 \(1\)
这样可能会出现每个部分内一个点都没选,但我们发现这样没关系。
因为任意选择一个点都是可行的,不会影响其他部分,也不会让边变得不合法。

点击查看

#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

const int _ = 5e6 + 7;

int n, m, k, w;
int dfn[_], low[_], idx, scc, bel[_], stk[_], top; bool in[_];
std::vector <int> g[_], p;

void Tarjan(int u) {
    dfn[u] = low[u] = ++idx, stk[++top] = u, in[u] = true;
    for (int v : g[u]) {
        if (!dfn[v]) Tarjan(v), low[u] = std::min(low[u], low[v]);
        else if (in[v]) low[u] = std::min(low[u], dfn[v]);
    }
    if (low[u] == dfn[u]) { ++scc; int nw;
        do {
            nw = stk[top--], bel[nw] = scc, in[nw] = false;
        } while (nw != u);
            
    }
}
int id(int x, int i) { return x + i * n; } //0 不选 1 选 2 前缀不选 3 后缀不选

int main() {
    scanf("%d%d%d", & n, & m, & k); int a, b;
    lep(i, 1, m) scanf("%d%d", & a, & b),
        g[id(a, 0)].push_back(id(b, 1)),
        g[id(b, 0)].push_back(id(a, 1));
    lep(i, 1, k) {
        scanf("%d", & w);
        while (w--) scanf("%d", & a), p.push_back(a);
        for (int k = 0; k < (int)p.size(); ++k) {
            g[id(p[k], 2)].push_back(id(p[k], 0)),
            g[id(p[k], 3)].push_back(id(p[k], 0));
            if (k)
                g[id(p[k], 2)].push_back(id(p[k - 1], 2)),
                g[id(p[k], 1)].push_back(id(p[k - 1], 2));
            if (k != p.size() - 1)
                g[id(p[k], 3)].push_back(id(p[k + 1], 3)),
                g[id(p[k], 1)].push_back(id(p[k + 1], 3));
        }
        p.clear();
    }

    lep(i, 1, 4 * n) if (!dfn[i]) Tarjan(i);
    lep(i, 1, n) if (bel[i] == bel[i + n]) { puts("NIE"); return 0; }
    puts("TAK");
    return 0;
}

P7477 「C.E.L.U-02」划分可重集

容易发现,每个元素只会属于一个集合,因为多属于一个意味着更多的限制。
看到题目肯定容易想到二分。
然后我们考虑如何将限制显式地连出来,发现满足存在限制的点对形式如同二维偏序,所以我们用 \(CDQ\) 来优化建图。
具体地,以 \(A\) 集合为例,对于 \(CDQ\) 分治的每一层,发现连边形如,左侧前缀向右侧单点连,右侧单点向左侧前缀连。
再加上前缀优化建图即可。
\(B\) 集合同理,不过变成后缀优化建图。
不要用主席树,空间会倒闭。

点击查看

#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

const int _ = 1e6 + 7;
const int inf = 1e9;
typedef long long ll;

enum { A, B }; int bel[_]; bool vis[_];
int n, m, w[_], x[_], y[_], ud[_], idx, scc, d[_], pi[_], po[_], ni[_], no[_];
std::vector <int> e[_], g[_];

void Add(int u, int v) { e[u].push_back(v), g[v].push_back(u); }
int id(int x, int op) { return x + op * n; }
void cdq(int l, int r, int k) {
	if (l == r) return; int md = (l + r) >> 1;
	cdq(l, md, k), cdq(md + 1, r, k);
	auto cmp = [](const int& x, const int& y) { return w[x] < w[y]; };
	std::sort(ud + l, ud + md + 1, cmp), std::sort(ud + md + 1, ud + r + 1, cmp);
	lep(i, l, md) {
		pi[i] = ++idx, po[i] = ++idx;
		if (i != l) Add(pi[i], pi[i - 1]), Add(po[i - 1], po[i]);
		Add(pi[i], id(ud[i], B)), Add(id(ud[i], A), po[i]);
	}
	rep(i, md, l) {
		ni[i] = ++idx, no[i] = ++idx;
		if (i != md) Add(ni[i], ni[i + 1]), Add(no[i + 1], no[i]);
		Add(ni[i], id(ud[i], A)), Add(id(ud[i], B), no[i]);
	}
	int R = l - 1;
	lep(i, md + 1, r) {
		while (R < md and w[ud[R + 1]] <= w[ud[i]] - k) ++R;
		if (R >= l and w[ud[R]] <= w[ud[i]] - k) Add(id(ud[i], A), pi[R]), Add(po[R], id(ud[i], B));
	}
	R = md + 1;
	rep(i, r, md + 1) {
		while (R > l and w[ud[R - 1]] >= w[ud[i]] + k) --R;
		if (R <= md and w[ud[R]] >= w[ud[i]] + k) Add(id(ud[i], B), ni[R]), Add(no[R], id(ud[i], A));
	}
}
void lnk(int k) {
	lep(i, 1, 2 * n) ud[i] = i; idx = 2 * n;
	cdq(1, n, k);
	lep(i, 1, m) Add(id(x[i], A), id(y[i], B)), Add(id(x[i], B), id(y[i], A)),
		Add(id(y[i], A), id(x[i], B)), Add(id(y[i], B), id(x[i], A));
}
void dfs1(int u) { vis[u] = true; for (int v : e[u]) if (!vis[v]) dfs1(v); d[++*d] = u; }
void dfs2(int u) { bel[u] = scc; for (int v : g[u]) if (!bel[v]) dfs2(v); }
void cls() { lep(i, 1, idx) vis[i] = bel[i] = 0, e[i].clear(), g[i].clear(); }
bool Check(int k) {
	lnk(k);
	lep(i, 1, idx) if (!vis[i]) dfs1(i);
	rep(i, idx, 1) if (!bel[d[i]]) ++scc, dfs2(d[i]);
	*d = scc = 0;
	lep(i, 1, n) if (bel[id(i, A)] == bel[id(i, B)]) { cls(); return false; }
	lep(i, 1, m) if (bel[id(x[i], A)] == bel[id(y[i], A)] or bel[id(x[i], B)] == bel[id(y[i], B)]) { cls(); return false; }
	cls(); return true;
}

int main() {
	scanf("%d%d", & n, & m);
	lep(i, 1, n) scanf("%d", w + i);
	lep(i, 1, m) scanf("%d%d", x + i, y + i);

	int l = 0, r = inf;
	while (l < r) {
		int mid = (l + r) >> 1;
		if (Check(mid)) r = mid;
		else l = mid + 1;
	}

	printf("%d\n", r == inf ? -1 : r);
	return 0;
}


P6965 [NEERC 2016] Binary Code

看到前缀相关,考虑建 \(01\) \(tire\)
然后对于每个含有 \(?\) 的串,填 \(0\) 和 填 \(1\) 作为两个文字,生成的两个串插入 \(01\) \(trie\) 中。
然后每个限制形如,一个点选了,那么和它位置相同的或者位置具有祖先后代关系的不能选。
发现 \(tire\) 本身就具有良好的结构,树上前缀和优化建图,对于每个节点内部再加上前后缀优化建图即可。

点击查看

#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

const int _ = 5e6 + 7;
typedef long long ll;

int n, len[_], nw, pos[_]; char s[_];
int ch[_][2], idx = 1, tot;
std::vector <int> Ed[_]; int pi[_], ni[_], oi[_], po[_], dl[_], dr[_];
std::vector <int> e[_], g[_]; int scc, bel[_], d[_]; bool vis[_];

void Add(int u, int v) { e[u].push_back(v), g[v].push_back(u); }
void dfs1(int u) { vis[u] = true; for (int v : e[u]) if (!vis[v]) dfs1(v); d[++*d] = u; }
void dfs2(int u) { bel[u] = scc; for (int v : g[u]) if (!bel[v]) dfs2(v); }
void Ins(char s[], int id) {
	int nw = 1, len = std::strlen(s) - 1;
	lep(i, 0, len) { int k = s[i] - '0';
		if (!ch[nw][k]) ch[nw][k] = ++idx;
		nw = ch[nw][k];
	}
	Ed[nw].push_back(id);
}
void Lnk(int u, int lst) {
	if (Ed[u].size()) {
		for (int i = 0; i < (int)Ed[u].size(); ++i) { 
			dl[i] = ++tot, dr[i] = ++tot, po[i] = ++tot;
			if (i) Add(dl[i], dl[i - 1]), Add(dr[i - 1], dr[i]), Add(po[i], po[i - 1]);
			Add(dl[i], Ed[u][i] ^ 1), Add(dr[i], Ed[u][i] ^ 1), Add(Ed[u][i], po[i]);
			if (i) Add(Ed[u][i], dl[i - 1]), Add(Ed[u][i - 1], dr[i]);
		}
		pi[u] = ++tot, oi[u] = ++tot, ni[u] = po[0];
		Add(pi[u], dr[0]), Add(oi[u], dr[0]);
		if (lst) Add(ni[u], pi[lst]), Add(pi[u], pi[lst]), Add(ni[lst], oi[u]), Add(oi[lst], oi[u]);
		lst = u;
	}
	if (ch[u][0]) Lnk(ch[u][0], lst);
	if (ch[u][1]) Lnk(ch[u][1], lst);
}
void Solve() {
	*d = 1;
	lep(i, 2, tot) if (!vis[i]) dfs1(i);
	rep(i, tot, 2) if (!bel[d[i]]) ++scc, dfs2(d[i]);
}

int main() {
	scanf("%d", & n);
	nw = 1;
	lep(i, 1, n) {
		scanf("%s", s + nw); len[i] = std::strlen(s + nw);
		lep(j, nw, nw + len[i] - 1) if (s[j] == '?') { pos[i] = j; break; }
		if (pos[i]) s[pos[i]] = '0', Ins(s + nw, i << 1), s[pos[i]] = '1', Ins(s + nw, i << 1 | 1);
		else Ins(s + nw, i << 1), Ins(s + nw, i << 1 | 1);
		nw += len[i] + 1;
	} tot = n << 1 | 1;

	Lnk(1, 0), Solve();
	lep(i, 1, n) if (bel[i << 1 | 1] == bel[i << 1]) { puts("NO"); return 0; }
	puts("YES");
	nw = 1;
	lep(i, 1, n) {
		if (pos[i] and bel[i << 1] > bel[i << 1 | 1]) s[pos[i]] = '0';
		else if (pos[i]) s[pos[i]] = '1';
		printf("%s\n", s + nw); nw += len[i] + 1;
	}
	return 0;
}


P9139 [THUPC 2023 初赛] 喵了个喵 II

先考虑如果每个元素恰好出现 \(2\) 次怎么分,发现将 \(i\) 两次出现的位置记作 \((l_i, r_i)\) ,那么有解的充要条件是:不存在两个区间相互包含。

必要性显然,充分性直接将所有的 \(l_i\) 作为一个子序列,所有的 \(r_i\) 作为另一个就好了。

但是现在每个元素出现 \(4\) 次,我们考虑修改一些这个做法。
可以枚举这 \(4\) 个位置的划分顺序,将他们看作两种不同的颜色,发现只有三种划分情况:

  • \(\color{red} 1\) \(\color{red} 2\) \(\color{green} 3\) \(\color{green} 4\)
  • \(\color{red} 1\) \(\color{green} 2\) \(\color{red} 3\) \(\color{green} 4\)
  • \(\color{red} 1\) \(\color{green} 2\) \(\color{green} 3\) \(\color{red} 4\)

且第三种情况以及出现了包含,所以只有两种方案,转化为 \(2-SAT\) 问题。
对于限制,发现依旧是二维偏序的形式,\(CDQ\) 优化建图即可。

点击查看

#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

const int _ = 5e6 + 7;
typedef long long ll;

struct node { int l, r, id; }s[_]; int len;
int n, a[_], pos[_][4], idx, pi[_], po[_];
std::vector <int> e[_], g[_]; int bel[_], scc, d[_]; bool vis[_], ans[_];

void Add(int u, int v) { e[u].push_back(v), g[v].push_back(u); }
void CDQ(int l, int r) {
	if (l == r) return; int md = (l + r) >> 1;
	CDQ(l, md), CDQ(md + 1, r);
	auto cmp = [](const node&x, const node&y) { return x.l > y.l; };
	std::sort(s + l, s + md + 1, cmp), std::sort(s + md + 1, s + r + 1, cmp);
	lep(i, l, md) {
		pi[i] = ++idx, po[i] = ++idx;
		if (i != l) Add(pi[i], pi[i - 1]), Add(po[i - 1], po[i]);
		Add(pi[i], s[i].id ^ 1), Add(s[i].id, po[i]);
	}
	int R = l - 1;
	lep(i, md + 1, r) {
		while (R < md and s[R + 1].l >= s[i].l) ++R;
		if (R >= l and s[R].l >= s[i].l) Add(s[i].id, pi[R]), Add(po[R], s[i].id ^ 1);
	}
}
void Dfs1(int u) { vis[u] = true; for (int v : e[u]) if (!vis[v]) Dfs1(v); d[++*d] = u; }
void Dfs2(int u) { bel[u] = scc; for (int v : g[u]) if (!bel[v]) Dfs2(v); }

int main() {
	scanf("%d", & n);
	lep(i, 1, n * 4) {
		scanf("%d", a + i);
		lep(j, 0, 3) if (!pos[a[i]][j]) { pos[a[i]][j] = i; break; }
	}
	lep(i, 1, n) s[++len] = { pos[i][0], pos[i][2], i << 1 | 1 }, s[++len] = { pos[i][1], pos[i][3], i << 1 | 1 },
		s[++len] = { pos[i][0], pos[i][1], i << 1 }, s[++len] = { pos[i][2], pos[i][3], i << 1 };
	idx = n << 1 | 1;
	std::sort(s + 1, s + 1 + len, [](const node&x, const node&y) { return x.r != y.r ? x.r < y.r : x.l > y.l; });
	CDQ(1, len);

	*d = 1;
	lep(i, 2, idx) if (!vis[i]) Dfs1(i);
	rep(i, idx, 2) if (!bel[d[i]]) ++scc, Dfs2(d[i]);
	
	lep(i, 1, n) if (bel[i << 1 | 1] == bel[i << 1]) { puts("No"); return 0; }
	puts("Yes");
	lep(i, 1, n) {
		if (bel[i << 1 | 1] > bel[i << 1]) ans[pos[i][2]] = ans[pos[i][3]] = true;
		else ans[pos[i][1]] = ans[pos[i][3]] = true;
	}
	lep(i, 1, n * 4) putchar('0' + ans[i]);
	puts("");
	return 0;
}


posted @ 2025-07-18 21:54  qkhm  阅读(13)  评论(0)    收藏  举报