洛谷

洛谷题单指南-连通性问题-P3825 [NOI2017] 游戏

原题链接:https://www.luogu.com.cn/problem/P3825

题意解读:

解题思路:

1、问题分析

  对于每种地图a、b、c只支持两种车,因此可以转化为2-SAT问题,但是地图x可以支持任意车,由于x的数量不多,可以暴搜所有的可能,时间复杂度最坏是3^8=6561,对于每一种可能都要跑一次2-SAT,时间复杂度级别为10^5 * 6561 > 10^8,依然不可行。

  其实,对于x,并不需要枚举a、b、c三种情况,只需要枚举a、b两种情况,就可以覆盖所有车的方案,因此对于a可以支持B/C两种车,对于b可以支持A/C两种车,综合起来就覆盖到了A、B、C三种车。这样时间复杂度级别在10^5 * 2^8 ≈ 10^7,可行。

2、问题转化

接下来,就要看问题模型如何转化成2-SAT。

设第i场游戏用车对应的节点为p = i 或 ¬p = i + n,

对于地图a,p表示用车B,¬p表示用车C;
对于地图b,p表示用车A,¬p表示用车C;
对于地图c,p表示用车A,¬p表示用车B。
对于四元组 (i,hi​,j,hj​),设第i场用车是hi对应节点是u,第j场用车是hj对应的节点是v,节点转换规则为:

对于地图a,用车hi = B对应节点u = i,用车hi = C对应节点u = i + n
对于地图b,用车hi = A对应节点u = i,用车hi = C对应节点u = i + n
对于地图c,用车hi = A对应节点u = i,用车hi = B对应节点u = i + n
如果第i场地图是hi,意味着第i场用车不能是hi,因此可以忽略;

如果第j场地图是hj,意味着第j场用车不能是hj,那么当第i用车是hi,肯定无解,可以建立u -> ¬u的有向边,表示矛盾;

如果第i场地图不是hi,当第i场用车hi,必有第j场用车hj,建立u -> v的有向边,同时建立¬v -> ¬u的有向边。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005, M = 300005;
int head[N], to[M], nxt[M], idx;
int dfn[N], low[N], timestamp;
stack<int> stk;
bool vis[N];
int scc[N], cnt;
int n, d, m;
string s;
int id[N], total; //id[i]表示s[i]是s中第几个x
char path[N]; //path[0] ~ path[d-1]是所有x的一种可能的选项排列
struct Require
{
    int i;
    char hi;
    int j;
    char hj;
} req[M];

//获取s[pos]的地图
char get(int pos)
{
    if(s[pos] == 'x') return path[id[pos]];
    else return s[pos];
}

//获取第pos辆车选用c时的节点
int node(int pos, char c)
{
    char mp = get(pos);
    if(mp == 'a')
    {
        if(c == 'B') return pos;
        else return pos + n;
    }
    else if(mp == 'b')
    {
        if(c == 'A') return pos;
        else return pos + n;
    }
    else
    {
        if(c == 'A') return pos;
        else return pos + n;
    }
}

//获取第pos辆车,在条件ok时的值
char car(int pos, bool ok)
{
    char mp = get(pos);
    if(mp == 'a')
    {
        if(ok) return 'B';
        else return 'C';
    }
    else if(mp == 'b')
    {
        if(ok) return 'A';
        else return 'C';
    }
    else 
    {
        if(ok) return 'A';
        else return 'B';
    }
}

//节点取反的节点
int neg(int id)
{
    if(id <= n) return id + n;
    else return id - n;
}

//建立有向边a->b
void add(int a, int b)
{
    to[++idx] = b;
    nxt[idx] = head[a];
    head[a] = idx;
}

//根据2-SAT模型建图
void build()
{
    for(int k = 1; k <= m; k++)
    {
        //如果第i场地图是hi,意味着第i场用车不能是hi,因此可以忽略;
        if(get(req[k].i) - 32 == req[k].hi) continue;
        //如果第j场地图是hj,意味着第j场用车不能是hj,那么当第i用车是hi,肯定无解,可以建立u -> ¬u的有向边,表示矛盾;
        if(get(req[k].j) - 32 == req[k].hj) 
        {
            int u = node(req[k].i, req[k].hi);
            add(u, neg(u));
            continue;
        }
        //如果第i场地图不是hi,当第i场用车hi,必有第j场用车hj,建立u -> v的有向边,同时建立¬v -> ¬u的有向边。
        int u = node(req[k].i, req[k].hi);
        int v = node(req[k].j, req[k].hj);
        add(u, v), add(neg(v), neg(u));
    }
}

//清空数组
void clear()
{
    memset(head, 0, sizeof(head));
    idx = 0;
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(scc, 0, sizeof(scc));
    cnt = 0;
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++timestamp;
    stk.push(u);
    vis[u] = true;
    for(int i = head[u]; i; i = nxt[i])
    {
        int v = to[i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(vis[v]) low[u] = min(low[u], dfn[v]);
    }
    if(low[u] == dfn[u])
    {
        cnt++;
        while(true)
        {
            int t = stk.top(); stk.pop();
            vis[t] = false;
            scc[t] = cnt;
            if(t == u) break;
        }
    }
}

bool check()
{
    bool ok = true;
    for(int i = 1; i <= n; i++)
    {
        if(scc[i] == scc[i + n])
        {
            ok = false;
            break;
        }
    }
    if(!ok) 
    {
        return false;
    }
    for(int i = 1; i <= n; i++)
    {
        if(scc[i] < scc[i + n]) cout << car(i, true);
        else cout << car(i, false);
    }
    cout << endl;
    return true;
}

//枚举所有x取A或B的排列组合
void dfs(int k)
{
    if(k > d)
    {
        //对于一个排列,执行2-SAT
        clear();
        build(); //建图
        for(int i = 1; i <= 2 * n; i++)
            if(!dfn[i]) 
                tarjan(i);

        if(check()) exit(0); //找到解则结束程序
        return;
    }
    path[k] = 'a'; dfs(k + 1);
    path[k] = 'b'; dfs(k + 1);
}

int main()
{
    cin >> n >> d >> s >> m;
    s = " " + s; //字符串从1开始
    for(int i = 1; i <= m; i++)     
        cin >> req[i].i >> req[i].hi >> req[i].j >> req[i].hj;
    for(int i = 1; i <= s.size(); i++)
        if(s[i] == 'x')
            id[i] = ++total;

    dfs(1);
    cout << -1;
    return 0;
}