yuwj  

碎碎念:
一道我执着于图上找路径的思路但是一直TLE + MLE + WA
这个题目学了5h,还以为自己终于要独立解决cf的D题了,但是终究还是败了...
也涨了教训,可以DP解决的题目,大数据不可能建图的,肯定要爆的,至少这个题目不行

题意:
从1交换到n,有3个人可以交换,每个人对于每张牌都有喜爱值,只有喜爱值更大的才会交换,能交换到n就输出方式,否则NO

思路:
先讲讲我调了5h失败的思路:
根据j比i,并且p[j] < p[i]的所有下标连边,建有向图,每个结点存放下标,边为人的编号,然后在这张图上找一条路径从结点1到结点n
很简单,很直接对吧,但是...
我的MLE代码贴贴吧,毕竟是还能通过test5,而且我也调了很久

点击查看代码
/*
有向图的连通性

细节:
如果我能知道对于每个数字i,j>i && p[j] < p[i] 有哪些数字就好了

桶:

*/
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i = a; i <= b; i++)
char match[3] = {'q','k','j'};

struct BIT{
    int n;
    vector<vector<int>> tree;

    BIT(int _n): n(_n)
    {
        tree.resize(n+1);
    }

    inline int lowbit(int x){return x&(-x);}

    void update(int x, int id)
    {
        for(int k = x; k <= n; k += lowbit(k))
        {
            tree[k].push_back(id);
        }
    }

    vector<int> query(int x)
    {
        vector<int> ret;
        for(int k = x; k; k -= lowbit(k))
        {
            ret.insert(ret.end(), tree[k].begin(), tree[k].end());
        }
        return ret;
    }
};

void solve()
{
    int n; cin >> n;
    vector<vector<pair<int,int>>> g(n+1);
    vector<vector<int>> mp(3,vector<int>(n+1));
    vector<int> p;
    rep(i,0,2) rep(j,1,n) cin >> mp[i][j];

    rep(k,0,2){
        BIT bit(n);
        vector<vector<int>> ans(n+1);
        
        for(int i = n; i>=1; i--)
        {
            int x = mp[k][i];
            if(x > 1)
            {
                ans[i] = bit.query(x-1);
                for(auto j : ans[i])g[i].push_back({j,k});
            }
            bit.update(x, i);
        }
    }

    vector<int> pre(n + 1, -1), from(n + 1, -1);
        vector<char> vis(n + 1, 0);
        queue<int> q;  q.push(1); vis[1] = 1;

        while(!q.empty() && !vis[n]){
            int u = q.front(); q.pop();
            for(auto [v, k] : g[u])
                if(!vis[v]){
                    vis[v] = 1,pre[v] = u, from[v] = k;
                    q.push(v);
                }
        }

        if(!vis[n]){
            cout << "NO\n";
            return;
        }
        vector<pair<int,int>> path;
        for(int v = n; v != 1; v = pre[v])
            path.push_back({v, from[v]});
        reverse(path.begin(), path.end());

        cout << "YES\n" << path.size() << '\n';
        for(auto [v, k] : path)
            cout << match[k] << ' ' << v << '\n';
    
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    int t; cin >> t; while(t--)
    solve();return 0;
}

然后又问了GPT问到了一个线段树找区间最小值 + 单点修改的做法,但是还不是建图
属于大炮打蚊子了

点击查看代码

#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
const char role[3] = {'q','k','j'};

struct Seg {
    int n;
    vector<int> t;                           
    Seg(int _n = 0): n(_n), t(4 * _n, INF) {}

    void build(int p, int l, int r, const vector<int>& leaf){
        if(l == r){ t[p] = leaf[l]; return; }
        int mid = (l + r) >> 1;
        build(p<<1, l, mid, leaf);
        build(p<<1|1, mid+1, r, leaf);
        t[p] = min(t[p<<1], t[p<<1|1]);
    }

    int query(int p, int l, int r, int ql, int qr){
        if(ql > r || qr < l) return INF;
        if(ql <= l && r <= qr) return t[p];
        int mid = (l + r) >> 1;
        return min(query(p<<1,l,mid,ql,qr),
                   query(p<<1|1,mid+1,r,ql,qr));
    }

    void update(int p, int l, int r, int idx, int val){
        if(l == r){ t[p] = val; return; }
        int mid = (l + r) >> 1;
        if(idx <= mid) update(p<<1, l, mid, idx, val);
        else           update(p<<1|1, mid+1, r, idx, val);
        t[p] = min(t[p<<1], t[p<<1|1]);
    }
};

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int T;  cin >> T;
    while(T--){
        int n;  cin >> n;

        vector<vector<int>> pref(3, vector<int>(n + 1));
        for(int k=0;k<3;++k)
            for(int pos=1, card; pos<=n; ++pos){
                cin >> pref[k][pos];
            }

        vector<vector<int>> leaf(3, vector<int>(n + 1, INF));
        for(int k=0;k<3;++k)
            for(int idx=1; idx<=n; ++idx){
                int val = pref[k][idx];      
                leaf[k][val] = idx;
            }

        vector<Seg> seg;
        seg.reserve(3);
        for(int k=0;k<3;++k){
            seg.emplace_back(n);
            seg[k].build(1, 1, n, leaf[k]);
        }

        vector<int> pre(n+1,-1), via(n+1,-1);
        vector<char> vis(n+1,0);
        queue<int> q;  q.push(1); vis[1]=1;

        while(!q.empty() && !vis[n]){
            int u = q.front(); q.pop();

            for(int k=0;k<3;++k){
                int lim = pref[k][u] - 1;         
                while(lim >= 1){
                    int v = seg[k].query(1,1,n,1,lim);   
                    if(v==INF) break;                    

                    int idx_v = pref[k][v];
                    seg[k].update(1,1,n,idx_v,INF);

                    if(v <= u) continue;                 
                    if(vis[v])  continue;                

                    vis[v]=1; pre[v]=u; via[v]=k; q.push(v);
                    if(v==n) break;
                }
            }
        }

        if(!vis[n]){
            cout << "NO\n";
            continue;
        }
        vector<pair<int,int>> path;
        for(int v=n; v!=1; v=pre[v]) path.push_back({v, via[v]});
        reverse(path.begin(), path.end());

        cout << "YES\n" << path.size() << '\n';
        for(auto [card,k] : path)
            cout << role[k] << ' ' << card << '\n';
    }
    return 0;
}

然后我在提交里面找到了这个图的做法,不是显示建图,所以规避了TLE + MLE,来学习一下
好吧,不愧是红名,我看不懂,即使在AI的辅助之下...

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

/*------------------------------------------------------------
  输入常数与辅助
------------------------------------------------------------*/
const int MAXN = 300000;      // 题目保证 n ≤ 3e5
string s = "Xqkj";            // 下标 1→'q',2→'k',3→'j'(0 位随便放个占位)

/*------------------------------------------------------------
  全局数组
  a[k][i]   : 玩家 k 对牌 i 的喜爱值(1..n,越大越喜欢)
  w[k]      : 在“目前可达的牌”里,对玩家 k 来说喜爱值最大的那张牌的喜爱值
  cur[k]    : 拿到 w[k] 这张牌时 “最后一次操作” 的信息 (玩家 id, 牌号)
              ——相当于“当前可达集合”里,玩家 k 的“代表边”
  fa[k][i]  : 如果能通过玩家 k 把某张牌换到 i,那么 fa[k][i] 存储
              那张牌是通过 (player, card) 这一步换到的
              用来回溯完整路径
------------------------------------------------------------*/
int  a[4][MAXN + 5];         // 1..3 × 1..n
int  w[4];                   // w[1..3]
pair<int,int> cur[4];        // cur[1..3]  (player, card)
pair<int,int> fa[4][MAXN + 5];
vector<pair<char,int>> op;   // 最终输出的交换序列

/*------------------------------------------------------------
  dfs 递归回溯:沿 fa[] 把路径翻转回来
  输入 (x = 玩家, y = 牌号)
  作用:把从 1→…→y 的那段路径加到 op[]
------------------------------------------------------------*/
void dfs(int x,int y){
    if (!x) return;                  // x=0 相当于空前驱,递归终点
    auto [p, c] = fa[x][y];          // 上一步是 (玩家 p, 牌 c)
    dfs(p, c);                       // 递归到更早的那一步
    op.push_back({ s[x], y });       // 把当前这一步压栈
}

/*------------------------------------------------------------
  主程序
------------------------------------------------------------*/
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);

    int T;  cin >> T;
    while (T--){
        int n; cin >> n;

        /* ---------- 1. 读入喜爱值 ---------- */
        for (int k = 1; k <= 3; ++k){
            for (int i = 1; i <= n; ++i){
                cin >> a[k][i];
                fa[k][i] = { -1, -1 };   // 先把前驱置成“不存在”
            }
            // cur[k] 初始指向 (0, 1):表示“牌 1 已在手上”,
            // player=0 作为哨兵(没有哪位玩家换到牌 1)
            cur[k] = { 0, 1 };
            w[k]   = a[k][1];            // 牌 1 的喜爱值
        }

        /* ---------- 2. 一次扫描所有牌号 i = 2 .. n ---------- */
        for (int i = 2; i <= n; ++i){
            for (int j = 1; j <= 3; ++j){           // 枚举出手玩家 j
                if (w[j] > a[j][i]){                // 条件① 玩家 j 愿意换 (更喜欢手里牌)
                    /* 建立一条“隐式边”:cur[j] -> (j, i) */
                    fa[j][i] = cur[j];              // 记录前驱 (玩家, 牌)

                    /* 把牌 i 纳入“已可达”集合:
                       它可能成为其他玩家 (k) 今后出手的新起点 */
                    for (int k = 1; k <= 3; ++k){
                        if (a[k][i] >  w[k]){       // 条件② 玩家 k 更喜欢牌 i
                            cur[k] = { j, i };       // 更新玩家 k 的代表边
                            w[k]   = a[k][i];       // 刷新最大喜爱值
                        }
                    }
                }
            }
        }

        /* ---------- 3. 从牌 n 开始回溯路径 ---------- */
        op.clear();                      // 存放答案
        for (int k = 1; k <= 3; ++k){
            if (fa[k][n].first != -1){   // 找到任意一个能到 n 的入边
                dfs(k, n);               // 递归回溯 1→...→n
                break;
            }
        }

        /* ---------- 4. 输出 ---------- */
        if (op.empty()){
            cout << "NO\n";              // 没路径
        }else{
            cout << "YES\n" << op.size() << '\n';
            for (auto [x, y] : op)
                cout << x << ' ' << y << '\n';
        }
    }
    return 0;
}

如果有人看到这篇博客,会建图解决这个题,或者看懂了大佬隐式图题解的欢迎交流
(尽管没人看(。•ˇ‸ˇ•。))

然后我又跑到了洛谷看了别人的解法,发现还是有我这个思路差不多的,尽管不是建图,但是这个树状数组的使用值得琢磨
来看看这篇:
https://www.luogu.com.cn/problem/solution/CF2028D
https://codeforces.com/contest/2028/submission/290962834

看了一下,状态是否可达就行了,
【经验】值域树状数组,前后缀
然后找到路径逆序输出结束了,还是很清晰的
AI都去死吧

posted on 2025-05-11 10:28  xiaowang524  阅读(19)  评论(0)    收藏  举报