UVa12273/LA4958 Palindromic DNA
- 题目链接
- 题意
- 分析
- AC 代码
题目链接
本题是2010年icpc欧洲区域赛西南欧赛区的的E题
题意
DNA由四种核苷碱基A、G、T、C组成,它们形成环状排序。当今能对DNA进行修改(但不能同时对相邻位置进行修改):将某位置加/减1,比如将C加1变成A、将C减1变成T。现给出一个DNA序列并从中选出若干子序列,问能否将所有选出的子序列修改成回文形式。
分析
对每个子序列,要满足回文形式,则必然是原DNA序列某些位置最终要变成相同的,这可以用并查集处理。然后考虑每个集合里面原始的不同碱基情况:如果四种都有,显然无法修改,无解;如果有三种,或者有两种并且是相对碱基(AT/GC),则一定要把相对碱基都做修改;如果有两种并且是相邻碱基,则一定要修改一个并且另外一个不能修改;如果只有一种,则已经是回文形式,无需修改。
由不能同时对相邻位置进行修改联想到2-SAT,对于位置 i i i,用结点 2 i 2i 2i表示修改、结点 2 i + 1 2i+1 2i+1表示不修改,下面考虑怎么添加子句构建有向边:相邻位置添加子句 x i ∧ ¬ x i + 1 x_i∧\neg x_{i+1} xi∧¬xi+1,即连接 2 i → 2 i + 3 2i\rightarrow2i+3 2i→2i+3、 2 i + 2 → 2 i + 1 2i+2\rightarrow2i+1 2i+2→2i+1两条有向边;每个集合不同碱基有三种,或者有两种并且是相对碱基(AT/GC),那么AT/GC是一定要修改的,连接 2 i + i → 2 i 2i+i\rightarrow2i 2i+i→2i即可;每个集合不同碱基有两种并且是相邻碱基,则一定要修改一个并且另外一个不能修改,找到根 r r r,当位置 i ≠ r i\ne r i=r时需要建边,位置 i i i和位置 r r r的碱基不同则添加两个子句 x i ∧ ¬ x r x_i∧\neg x_r xi∧¬xr和 ¬ x i ∧ x r \neg x_i∧x_r ¬xi∧xr,相同则添加两个子句 x i ∧ x r x_i∧x_r xi∧xr和 ¬ x r ∧ ¬ x i \neg x_r∧\neg x_i ¬xr∧¬xi。
这里再说一下,对于限定了部分结点取值的2-sat问题,可以这样连边处理:如果结点 i i i必须为真(对应 2 i 2i 2i),则连边 2 i + i → 2 i 2i+i\rightarrow2i 2i+i→2i;如果结点必须为假,则连边 2 i → 2 i + 1 2i\rightarrow2i+1 2i→2i+1。
AC 代码
#include <iostream>
using namespace std;#define w(c) c=='A' ? 1 : (c=='G' ? 2 : (c=='T' ? 4 : 8))
#define M 20020
int g[M][M>>1], f[M>>1], h[M>>1], v[M>>1], c[M], s[M], sn[M], low[M], pre[M], cc, clk, n, p, t; char d[M>>1];int find(int x) {return x == f[x] ? x : f[x] = find(f[x]);
}void add_clause(int u, int v) {g[u][c[u]++] = v^1; g[v][c[v]++] = u^1;
}bool dfs(int u) {low[u] = pre[u] = ++clk; s[p++] = u;for (int i=0, v; i<c[u]; ++i) if (!pre[v = g[u][i]]) {if (!dfs(v)) return false;low[u] = min(low[u], low[v]);} else if (!sn[v]) low[u] = min(low[u], pre[v]);if (low[u] == pre[u]) {++cc;while (true) {if (cc == sn[s[--p]^1]) return false;sn[s[p]] = cc;if (s[p] == u) break;}}return true;
}bool solve() {cin >> d;for (int i=0; i<n; ++i) f[i] = i, h[i] = 0;while (t--) {int l, h; char _; cin >> l >> _; h = ++l >> 1;for (int i=1; i<l; ++i) {cin >> v[i];if (i > h) f[find(v[i])] = find(v[l-i]);}}t = n<<1;for (int u=0; u<t; ++u) c[u] = pre[u] = sn[u] = cc = p = clk = 0;for (int i=0; i<n; ++i) h[find(i)] |= w(d[i]);for (int i=0; i<n; ++i) {int j = f[i], v = h[j];if (v == 15) return false;if (i > 0) add_clause((i-1) << 1, i << 1);if ((v&5) == 5) {if (d[i]=='A' || d[i]=='T') j=i<<1 | 1, g[j][c[j]++] = j^1;} else if ((v&10) == 10) {if (d[i]=='G' || d[i]=='C') j=i<<1 | 1, g[j][c[j]++] = j^1;} else if (i != j && (v==3 || v==6 || v==9 || v==12)) {bool e = d[i] == d[j];add_clause(i<<1, e ? j<<1 | 1 : j<<1); add_clause(i<<1 | 1, e ? j<<1 : j<<1 | 1);}}for (int u=0; u<t; ++u) if (!pre[u] && !dfs(u)) return false;return true;
}int main() {ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);while (cin >> n >> t && n) cout << (solve() ? "YES" : "NO") << endl;return 0;
}