碎碎念:
一道我执着于图上找路径的思路但是一直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都去死吧

浙公网安备 33010602011771号