好烦。写一道题 TLE25次,感觉已经尽力了。。。留下来,以后做。
正好趁现在补下2-sat模板。前几天做2-sat,就一个有向图tarjan算法就敲了不下20遍,对这个算法理解的更加透彻了,而且还间接帮我理解了二分查找,收获不少。
2-sat 模板(对于DFS 暴力求最小字典序的模板,我就不写了,网上这个模板比较多)
一:判断是否存在可行解,若存在输出任意一组可行解。
思路:根据相对关系建边,先tarjan求SCC,判断相对关系的变量是否在同一个SCC里面。若所有变量均满足它和它相对变量不在同一个SCC里面,就说明存在可行解,反之不存在。判断后 ,就是SCC缩点 + 反向建边,然后拓扑排序 + 染色,染成同一种颜色就是一组可行解。
模板:
#include
#include
#include
#include
#include
#include
#define MAXN
#define MAXM //根据题目需求
#define eps 1e-5
using namespace std;
struct Edge
{
int from, to, next;
}edge[MAXM];//原图
int head[MAXN], edgenum;
vector G[MAXN];//缩点后新图
int in[MAXN];//新图SCC的入度
int low[MAXN], dfn[MAXN];//通过子女节点所能到达的最低深度优先数 深度优先数
int dfs_clock;//时间戳
int sccno[MAXN], scc_cnt;//sccno[i]表示i属于哪个SCC scc_cnt是SCC计数器
stack S;//存储当前SCC的点
bool Instack[MAXN];//判断点是否在栈里面
int color[MAXN];//染色
int fp[MAXN];//建立对应SCC编号的映射
void init()
{
edgenum = 0;
memset(head, -1, sizeof(head));
}
void addEdge(int u, int v)
{
Edge E = {u, v, head[u]};
edge[edgenum] = E;
head[u] = edgenum++;
}
void getMap()//建图
{
}
void tarjan(int u, int fa)//求SCC
{
int v;
low[u] = dfn[u] = ++dfs_clock;
S.push(u);
Instack[u] = true;
for(int i = head[u]; i != -1; i = edge[i].next)
{
v = edge[i].to;
if(!dfn[v])
{
tarjan(v, u);
low[u] = min(low[u], low[v]);
}
else if(Instack[v])
low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
scc_cnt++;
for(;;)
{
v = S.top(); S.pop();
Instack[v] = false;
sccno[v] = scc_cnt;
if(v == u) break;
}
}
}
void find_cut(int l, int r)
{
memset(low, 0, sizeof(low));
memset(dfn, 0, sizeof(dfn));
memset(sccno, 0, sizeof(sccno));
memset(Instack, false, sizeof(Instack));
dfs_clock = scc_cnt = 0;
for(int i = l; i <= r; i++)
if(!dfn[i]) tarjan(i, -1);
}
void suodian()//缩点 + 反向建图
{
for(int i = 1; i <= scc_cnt; i++) G[i].clear(), in[i] = 0;
for(int i = 0; i < edgenum; i++)
{
int u = sccno[edge[i].from];
int v = sccno[edge[i].to];
if(u != v)
G[v].push_back(u), in[u]++; //反向建图
}
}
void toposort()//拓扑排序 + 染色
{
queue Q;
memset(color, 0, sizeof(color));
for(int i = 1; i <= scc_cnt; i++) if(in[i] == 0) Q.push(i);
while(!Q.empty())
{
int u = Q.front();
Q.pop();
if(color[u] == 0)//没有颜色
{
color[u] = 1;//染色
color[fp[u]] = 2;
}
for(int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
if(--in[v] == 0)//入度减一
Q.push(v);
}
}
}
void solve()
{
memset(fp, 0, sizeof(fp));
for(int i = 0; i < N; i++)
{
if(sccno[2*i] == sccno[2*i + 1])
{
printf("无可行解\n");
return ;
}
else
{
fp[sccno[2*i]] = sccno[2*i + 1];
fp[sccno[2*i + 1]] = sccno[2*i];
}
}
suodian();//缩点
toposort();//拓扑排序 + 染色
printf("一组可行解:\n");
for(int i = 1; i < N; i++)
{
if(color[sccno[2*i]] == 1)//可行解
printf("");
}
printf("\n");
}
int main()
{
while("题目输入")
{
init();//初始化
getMap();//建图
find_cut(1, 2*N-1);//找SCC
solve();//判断是否存在可行解 或者 输出一组可行解
}
return 0;
}
二:在区间[a, b]寻找使得2-sat成立的最优解
思路:基于二分查找
二分代码如下:
1,整数解
void solve()//二分查找
{
int left, right, mid;
int ans;//最后结果
left = a, right = b;
while(right >= left)
{
mid = (left + right) / 2;
init();
getMap(mid);//根据当前值建图
find_cut(0, 2*N-1);//找SCC
if(two_sat())//判断当前是否可行
{
ans = mid;//更新
left = mid + 1;
}
else
right = mid - 1;
}
printf("%d\n", ans);
}
2,浮点数解:
#define eps //根据需求设置精度
void solve()//二分查找
{
double left, right, mid;
left = a, right = b;
while(right - left > eps)
{
mid = (left + right) / 2;
init();
getMap(mid);//根据当前值建图
find_cut(0, 2*N-1);//找SCC
if(two_sat())//判断当前是否可行
left = mid;
else
right = mid;
}
printf("%.3lf\n", left);//输出right 和 left 都可以
}