题解 P9697【[GDCPC2023] Canvas】

发布时间 2023-10-04 20:43:49作者: rui_er

好题。

后面的操作会覆盖前面的操作,这东西不好处理,我们不妨时光倒流,将问题转化为一个位置一旦被填了数,就再也不会变了。如果解决了这个问题,只需将操作序列倒过来,就得到了原问题的解。

显然,所有 \(x_i=y_i=2\) 的操作会最先被执行,所有 \(x_i=y_i=1\) 的操作会最后被执行。只需要给所有 \(x_i\ne y_i\) 的操作确定顺序即可,不妨设所有这样的操作都满足 \(x_i=1,y_i=2\)

一旦操作 \((u,1,v,2)\) 被执行,则所有形如 \((v,1,w,2)\) 的操作都可以被执行。在这一系列操作涉及到的位置中,除了 \(u\) 被填成了 \(1\),其他位置都被填成了 \(2\)。我们称 \((u,1,v,2)\)启动操作,称启动操作引发出的一系列操作为连锁操作。这种传递关系使我们想到图论建模。

对于每个形如 \((u,1,v,2)\) 的操作,连接一条从 \(u\) 指向 \(v\) 的有向边。将图缩点,显然我们只会在入度为 \(0\) 的强连通分量中选择恰好一条边作为启动操作,这条边的启动会导致它所在的强连通分量以及在它下游的所有强连通分量中的边成为连锁操作。我们不需要付出任何代价就可以把入度不为 \(0\) 的强连通分量全部填成 \(2\),而对于入度为 \(0\) 的强连通分量,我们最多只会将一个点填为 \(1\)

关于启动操作的选择还有一点要注意。如果一个入度为 \(0\) 的强连通分量中,已经有点被形如 \((u,2,v,2)\) 的操作钦定填 \(2\) 了,则以这个点的一条出边作为启动操作,就可以少将一个点填为 \(1\)(这就是上面说“最多”的原因)。

时间复杂度 \(O(n+m)\)

// Problem: T368340 [GDCPC2023] H-Canvas
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/T368340?contestId=135929
// Memory Limit: 1 MB
// Time Limit: 3000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x, y, z) for(int x = (y); x <= (z); ++x)
#define per(x, y, z) for(int x = (y); x >= (z); --x)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do {freopen(s".in", "r", stdin); freopen(s".out", "w", stdout);} while(false)
#define endl '\n'
using namespace std;
typedef long long ll;

mt19937 rnd(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
int randint(int L, int R) {
    uniform_int_distribution<int> dist(L, R);
    return dist(rnd);
}

template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}

const int N = 5e5 + 5;

int T, n, m, l[N], x[N], r[N], y[N], deg[N], vis[N], res[N], two[N];
vector<tuple<int, int>> e[N];
vector<int> ans, aft;

int dfn[N], low[N], tms, ins[N], col[N], scc;
stack<int> stk;

void tarjan(int u) {
    dfn[u] = low[u] = ++tms;
    stk.push(u);
    ins[u] = 1;
    for(auto [v, id] : e[u]) {
        if(!dfn[v]) {
            tarjan(v);
            chkmin(low[u], low[v]);
        }
        else if(ins[v]) {
            chkmin(low[u], dfn[v]);
        }
    }
    if(dfn[u] == low[u]) {
        ++scc;
        while(true) {
            int v = stk.top();
            stk.pop();
            ins[v] = 0;
            col[v] = scc;
            if(u == v) break;
        }
    }
}

void dfs(int u) {
    vis[u] = 1;
    for(auto [v, id] : e[u]) {
        ans.push_back(id);
        if(!vis[v]) dfs(v);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    for(cin >> T; T; --T) {
        cin >> n >> m;
        rep(i, 1, m) {
            cin >> l[i] >> x[i] >> r[i] >> y[i];
            if(x[i] == 2 && y[i] == 2) {
                ans.push_back(i);
                two[l[i]] = two[r[i]] = 1;
            }
            else if(x[i] == 1 && y[i] == 1) {
                aft.push_back(i);
            }
            else {
                if(x[i] > y[i]) {
                    swap(l[i], r[i]);
                    swap(x[i], y[i]);
                }
                e[l[i]].emplace_back(r[i], i);
            }
        }
        rep(u, 1, n) if(!dfn[u]) tarjan(u);
        rep(u, 1, n) for(auto [v, id] : e[u]) if(col[u] != col[v]) ++deg[col[v]];
        rep(u, 1, n) if(!deg[col[u]] && !vis[u] && two[u]) dfs(u);
        rep(u, 1, n) if(!deg[col[u]] && !vis[u]) dfs(u);
        for(int i : aft) ans.push_back(i);
        reverse(ans.begin(), ans.end());
        for(int i : ans) {
            res[l[i]] = x[i];
            res[r[i]] = y[i];
        }
        cout << accumulate(res + 1, res + 1 + n, 0) << endl;
        for(int i : ans) cout << i << " "; cout << endl;
        rep(i, 1, n) {
            deg[i] = vis[i] = res[i] = dfn[i] = low[i] = col[i] = two[i] = 0;
            e[i].clear();
        }
        ans.clear();
        aft.clear();
        tms = scc = 0;
    }
    return 0;
}