标签:top == 转换 tchar class har 方案 wing 目录
????有 n 个变量x[1...n],每个变量的可能取值为1或0(或称i和~i必取到其中1个)。
????给定 m 个约束条件,每个约束条件形如:
若 x[i] 取 i(或者~i),则 x[j] 必取 j(或者~j)
????判定是否存在对每个变量的合法赋值,使所有约束都被满足
判定方法:
本质:
????2-sat的本质就是判断一个xi的属性,因为xi只能是0或1,因此如果xi既是0也是1那么就是非法状态(scc[xi] == scc[~xi])。2-sat和扩展域并查集的本质相同,不同的在于适用条件不同,2-sat只需要一个条件能够推导出2个命题即可(原命题、逆否命题),而扩展域并查集则需要一个条件能够推导出4个命题(原命题、逆否命题、否命题、逆命题)。异或能够导出4个命题,与/或能导出2个命题。2-sat建立的是有向边,所以使用tarjan算法处理;扩展域并查集使用无向边,所以使用并查集处理。
建边技巧:
????建边的原则就是建边时一定要考虑原命题和它的逆否命题,建边分两种情况:
// 属于已知边关系建边
#include<bits/stdc++.h>
using namespace std;
int const N = 2e6 + 10, M = 2e6 + 10;
// dfn记录每个点的时间戳,low记录每个点的回溯值,scc[i]=x表示i在标号为x的强连通分量里,stk维护一个栈,sccnum记录强连通分量的个数
int dfn[N], low[N], scc[N], stk[N], sccnum, top, timestamp;
int h[N], e[M], ne[M], idx;
int n, m;
// a->b有一条边
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// tarjan算法求强连通分量
void tarjan(int root)
{
if (dfn[root]) return; // 时间戳不为0,返回
dfn[root] = low[root] = ++timestamp; // 记录当前点的时间戳和回溯值,初始化二者相同,而后dfn[root]>=low[root]
stk[++top] = root; // 把根放入栈内
for (int i = h[root]; i != -1; i = ne[i]) // 遍历每一个与根节点相邻的点
{
int j = e[i]; // 与i相邻的点为j
if (!dfn[j]) // j点没有访问过
{
tarjan(j); // 继续dfs,得到所有以j点为根的子树内所有的low和dfn
low[root] = min(low[root], low[j]); // 根的low是其子树中low最小的那个
}
else if (!scc[j]) // 如果j这个点还在栈内(在栈内的话不属于任何一个scc),同时一个栈内的点在一个scc内
{
low[root] = min(low[root], dfn[j]); // low代表所能到达的最小的时间戳
}
}
// 如果root的后代不能找到更浅的节点(更小的时间戳)
if (low[root] == dfn[root]) // 只有某个强连通分量的根节点的low和dfn才会相同
{
sccnum++;
while (1) // 出栈直到等于root
{
int x = stk[top--];
scc[x] = sccnum;
if (x == root) break;
}
}
}
int opp(int x) {
if (x > n) return x - n;
else return x + n;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1, a, b, c, d; i <= m; ++i) {
scanf("%d %d %d %d", &a, &b, &c, &d);
if (!b) a = opp(a);
if (!d) c = opp(c);
// a|c=1 => ~a->c, ~c->a
add(opp(a), c), add(opp(c), a);
}
// tarjan求scc
for (int i = 1; i <= n * 2; ++i)
if (!dfn[i]) tarjan(i);
// 判断是否满足条件
for (int i = 1; i <= n; ++i) {
if (scc[i] == scc[opp(i)]) {
cout << "IMPOSSIBLE\n";
return 0;
}
}
// 打印路径
cout << "POSSIBLE\n";
for (int i = 1; i <= n; ++i) {
if (scc[i] < scc[opp(i)]) cout << "1 ";
else cout << "0 ";
}
return 0;
}
acwing370卡图难题
有N个变量X0~XN?1,每个变量的可能取值为0或1。
给定M个算式,每个算式形如 Xa op Xb=c,其中 a,b 是变量编号,c 是数字0或1,op 是 and,or,xor 三个位运算之一。求是否存在对每个变量的合法赋值,使所有算式都成立。
/* 本题的建边需要好好牢记,&、|能够推出2个命题,^能够推出4个命题 */
#include<bits/stdc++.h>
using namespace std;
int const N = 1e3 + 10, M = 4e6 + 10;
// dfn记录每个点的时间戳,low记录每个点的回溯值,scc[i]=x表示i在标号为x的强连通分量里,stk维护一个栈,sccnum记录强连通分量的个数。该题属于已知边关系建边。
int dfn[N], low[N], scc[N], stk[N], sccnum, top, timestamp;
int h[N], e[M], ne[M], idx;
int n, m;
char op[10];
// a->b有一条边
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// tarjan算法求强连通分量
void tarjan(int root)
{
if (dfn[root]) return; // 时间戳不为0,返回
dfn[root] = low[root] = ++timestamp; // 记录当前点的时间戳和回溯值,初始化二者相同,而后dfn[root]>=low[root]
stk[++top] = root; // 把根放入栈内
for (int i = h[root]; i != -1; i = ne[i]) // 遍历每一个与根节点相邻的点
{
int j = e[i]; // 与i相邻的点为j
if (!dfn[j]) // j点没有访问过
{
tarjan(j); // 继续dfs,得到所有以j点为根的子树内所有的low和dfn
low[root] = min(low[root], low[j]); // 根的low是其子树中low最小的那个
}
else if (!scc[j]) // 如果j这个点还在栈内(在栈内的话不属于任何一个scc),同时一个栈内的点在一个scc内
{
low[root] = min(low[root], dfn[j]); // low代表所能到达的最小的时间戳
}
}
// 如果root的后代不能找到更浅的节点(更小的时间戳)
if (low[root] == dfn[root]) // 只有某个强连通分量的根节点的low和dfn才会相同
{
sccnum++;
while (1) // 出栈直到等于root
{
int x = stk[top--];
scc[x] = sccnum;
if (x == root) break;
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1, a, b, c; i <= m; ++i)
{
scanf("%d%d%d%s", &a, &b, &c, op);
getchar();
if(op[0]==‘A‘ && c==0){//a&b = 0, a->~b, b->~a
add(a, b + n);
add(b, a + n);
}
if(op[0]==‘A‘ && c==1){//a&b = 1, ~a->a, -b->b
add(a + n, a);
add(b + n, b);
}
if(op[0]==‘O‘ && c==0){//a|b = 0, a->~a, b->~b
add(a, a + n);
add(b, b + n);
}
if(op[0]==‘O‘ && c==1){//a|b = 1, ~a->b, ~b->a
add(a + n, b);
add(b + n, a);
}
if(op[0]==‘X‘ && c==0){//a^b = 0, a->b, ~a->~b, b->a, ~b->-a
add(a, b);
add(a + n, b + n);
add(b, a);
add(b + n, a + n);
}
if(op[0]==‘X‘ && c==1){//a^b = 1, a->~b, ~a->b, b->~a, ~b->a
add(a, b + n);
add(a + n, b);
add(b, a + n);
add(b + n, a);
}
}
// tarjan求scc
for (int i = 1; i <= n * 2; ++i)
if (!dfn[i]) tarjan(i);
// 判断是否满足条件
for (int i = 1; i <= n; ++i) {
if (scc[i] == scc[i + n]) {
cout << "NO";
return 0;
}
}
cout << "YES";
return 0;
}
acwing371牧师约翰最忙碌的一天
9月1日这天牧师需要忙碌婚礼的事情,有 N 对情侣在这天准备结婚,每对情侣都预先计划好了婚礼举办的时间,其中第 i 对情侣的婚礼从时刻 Si 开始,到时刻 Ti 结束。第 i 对情侣需要 Di 分钟完成这个仪式,即必须选择 Si~Si+Di 或 Ti?Di~Ti 两个时间段之一。现在给定时间可选时间段,求出是否存在合法方案,并打印路径
// 本题没有给出边的关系,但点的数目很少,直接枚举建边
// 属于边未知关系,枚举建边
#include<bits/stdc++.h>
using namespace std;
int const N = 2e3 + 10, M = N * N;
// dfn记录每个点的时间戳,low记录每个点的回溯值,scc[i]=x表示i在标号为x的强连通分量里,stk维护一个栈,sccnum记录强连通分量的个数
int dfn[N], low[N], scc[N], stk[N], sccnum, top, timestamp;
int h[N], e[M], ne[M], idx, d[N], n, m, t[N][2];
// a->b有一条边
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// tarjan算法求强连通分量
void tarjan(int root)
{
if (dfn[root]) return; // 时间戳不为0,返回
dfn[root] = low[root] = ++timestamp; // 记录当前点的时间戳和回溯值,初始化二者相同,而后dfn[root]>=low[root]
stk[++top] = root; // 把根放入栈内
for (int i = h[root]; i != -1; i = ne[i]) // 遍历每一个与根节点相邻的点
{
int j = e[i]; // 与i相邻的点为j
if (!dfn[j]) // j点没有访问过
{
tarjan(j); // 继续dfs,得到所有以j点为根的子树内所有的low和dfn
low[root] = min(low[root], low[j]); // 根的low是其子树中low最小的那个
}
else if (!scc[j]) // 如果j这个点还在栈内(在栈内的话不属于任何一个scc),同时一个栈内的点在一个scc内
{
low[root] = min(low[root], dfn[j]); // low代表所能到达的最小的时间戳
}
}
// 如果root的后代不能找到更浅的节点(更小的时间戳)
if (low[root] == dfn[root]) // 只有某个强连通分量的根节点的low和dfn才会相同
{
sccnum++;
while (1) // 出栈直到等于root
{
int x = stk[top--];
scc[x] = sccnum;
if (x == root) break;
}
}
}
// 判断是否矛盾
bool isx(int i, int fi, int j, int fj){
if(t[i][fi] >= t[j][fj]+d[j]) return 0;
if(t[i][fi]+d[i] <= t[j][fj]) return 0;
return 1;
}
int str2int(string s){
int x = 0;
x = (s[0]-‘0‘)*10 + (s[1]-‘0‘);
x *= 60;
x += (s[3]-‘0‘)*10 + (s[4]-‘0‘);
return x;
}
string int2str(int x){
string s = "00:00";
int h = x/60, m = x%60;
s[0] = ‘0‘ + h/10; s[1] = ‘0‘ + h%10;
s[3] = ‘0‘ + m/10; s[4] = ‘0‘ + m%10;
return s;
}
int main()
{
cin >> n;
memset(h, -1, sizeof h);
// 读入并进行时间转换
for (int i = 1; i <= n; ++i) {
string start, end;
cin >> start >> end >> d[i];
t[i][0] = str2int(start);
t[i][1] = str2int(end) - d[i];
}
// 枚举建边
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (i == j) continue;
// isx函数判断是否矛盾
if (isx(i, 0, j, 0)) add(i, j + n), add(j, i + n);
if (isx(i, 1, j, 0)) add(i + n, j + n), add(j, i);
if (isx(i, 0, j, 1)) add(i, j), add(j + n, i + n);
if (isx(i, 1, j, 1)) add(i + n, j), add(j + n, i);
}
}
// tarjan求scc
for (int i = 1; i <= n * 2; ++i)
if (!dfn[i]) tarjan(i);
// 判断是否满足条件
for (int i = 1; i <= n; ++i) {
if (scc[i] == scc[i + n]) {
cout << "NO\n";
return 0;
}
}
// 打印路径
cout << "YES\n";
for (int i = 1; i <= n; ++i) {
if (scc[i] < scc[i + n]) cout << int2str(t[i][0]) << " " << int2str(t[i][0] + d[i]) << endl;
else cout << int2str(t[i][1]) << " " << int2str(t[i][1] + d[i]) << endl;
}
return 0;
}
标签:top == 转换 tchar class har 方案 wing 目录
原文地址:https://www.cnblogs.com/spciay/p/13135069.html