ABC-273解题报告

D. LRUD Instructions

题意:一个左上角为 \((1,1)\)、右下角为 \((H,W)\) 的矩阵,矩阵中有 \(n\) 个障碍。你初始在 \((r,c)\),给你一个操作序列,每个操作为向上/下/左/右走若干格,如果遇到障碍/走到边界则停止。每次操作后输出当前位置。

用数据结构存下每行中的障碍和每列中的障碍,每次从中找到最近的障碍,如果被挡住则走到障碍的上一格,否则正常走。

best code:

By tourist

#include <bits/stdc++.h>

using namespace std;

#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  int h, w, r, c;
  cin >> h >> w >> r >> c;
  int n;
  cin >> n;
  map<int, set<int>> mr;
  map<int, set<int>> mc;
  for (int i = 0; i < n; i++) {
    int rr, cc;
    cin >> rr >> cc;
    mr[rr].insert(cc);
    mc[cc].insert(rr);
  }
  int q;
  cin >> q;
  while (q--) {
    string op;
    int cnt;
    cin >> op >> cnt;
    if (op[0] == 'R') {
      auto it = mr[r].lower_bound(c);
      if (it != mr[r].end()) {
        c = min(*it - 1, c + cnt);
      } else {
        c += cnt;
      }
    }
    if (op[0] == 'D') {
      auto it = mc[c].lower_bound(r);
      if (it != mc[c].end()) {
        r = min(*it - 1, r + cnt);
      } else {
        r += cnt;
      }
    }
    if (op[0] == 'L') {
      auto it = mr[r].lower_bound(c);
      if (it != mr[r].begin()) {
        c = max(*prev(it) + 1, c - cnt);
      } else {
        c -= cnt;
      }
    }
    if (op[0] == 'U') {
      auto it = mc[c].lower_bound(r);
      if (it != mc[c].begin()) {
        r = max(*prev(it) + 1, r - cnt);
      } else {
        r -= cnt;
      }
    }
    r = max(r, 1);
    r = min(r, h);
    c = max(c, 1);
    c = min(c, w);
    cout << r << " " << c << '\n';
  }
  return 0;
}

这份代码有两个好的地方:

  1. 使用 mapset 来维护障碍,避免了复杂的离散化处理。
  2. 将越界情况统一处理,每次操作最后使用四行 \(\min/\max\) 来处理越界。

By WA_TLE

#include "bits/stdc++.h"
using namespace std;
using namespace std::chrono;
typedef long long int llint;
typedef long double lldo;
#define mp make_pair
#define mt make_tuple
#define pub push_back
#define puf push_front
#define pob pop_back
#define pof pop_front
#define fir first
#define sec second
#define res resize
#define ins insert
#define era erase
#define REP(i, n) for(int i = 0;i < (n);i++)
/*cout<<fixed<<setprecision(20);cin.tie(0);ios::sync_with_stdio(false);*/
const llint mod=1000000007;
template<class T> void SO(T& ve){sort(ve.begin(),ve.end());}

int main(void){
	int H,W,rs,cs,i,j,n;cin>>H>>W>>rs>>cs>>n;

	vector<pair<int,int>>rc(n+2);
	vector<pair<int,int>>cr(n+2);
	for(i=0;i<n;i++){
		int r,c;cin>>r>>c;
		rc[i]=mp(r,c);
		cr[i]=mp(c,r);
	}
	rc[n]=mp(mod,mod);
	cr[n]=mp(mod,mod);
	rc[n+1]=mp(-mod,-mod);
	cr[n+1]=mp(-mod,-mod);
	SO(rc);
	SO(cr);
	int Q;cin>>Q;
	while(Q--){
		char d;int l;
		cin>>d>>l;
		if(d=='U'){//r--
			auto kabe=*prev(lower_bound(cr.begin(),cr.end(),mp(cs,rs)));
			rs-=l;
			chmax(rs,1);
			if(kabe.fir==cs){chmax(rs,kabe.sec+1);}
		}
		if(d=='D'){//r++
			auto kabe=*lower_bound(cr.begin(),cr.end(),mp(cs,rs));
			rs+=l;
			chmin(rs,H);
			if(kabe.fir==cs){chmin(rs,kabe.sec-1);}
		}
		if(d=='L'){//c--
			auto kabe=*prev(lower_bound(rc.begin(),rc.end(),mp(rs,cs)));
			cs-=l;
			chmax(cs,1);
			if(kabe.fir==rs){chmax(cs,kabe.sec+1);}
		}
		if(d=='R'){//c++
			auto kabe=*lower_bound(rc.begin(),rc.end(),mp(rs,cs));
			cs+=l;
			chmin(cs,W);
			if(kabe.fir==rs){chmin(cs,kabe.sec-1);}
		}
		cout<<rs<<" "<<cs<<endl;
	}
	return 0;
}

这份代码巧妙地利用了 pair 自带比较函数的特点,使用正反两个 pair 数组维护障碍,同样避免了离散化且实现巧妙。

By Factorio

#include <bits/stdc++.h>
using namespace std;
map<int, set<int> > r, c;
int n, m, sx, sy, o, x, y, q, l;
char d;
int main()
{
	cin >> n >> m >> sx >> sy >> o;
	for (int i = 0; i < o; i++)
	{
		cin >> x >> y;
		r[x].insert(y);
		c[y].insert(x);
	}
	cin >> q;
	for (int i = 0; i < q; i++)
	{
		cin >> d >> l;
		if (d == 'L')
		{
			r[sx].insert(0);
			int p = *--r[sx].upper_bound(sy);
			sy = max(sy - l, p + 1);
		}
		else if (d == 'R')
		{
			r[sx].insert(m + 1);
			int p = *r[sx].upper_bound(sy);
			sy = min(sy + l, p - 1);
		}
		else if (d == 'U')
		{
			c[sy].insert(0);
			int p = *--c[sy].upper_bound(sx);
			sx = max(sx - l, p + 1);
		}
		else
		{
			c[sy].insert(n + 1);
			int p = *c[sy].upper_bound(sx);
			sx = min(sx + l, p - 1);
		}
		printf("%d %d\n", sx, sy);
	}
	return 0;
}

这份代码的巧妙之处在于,每次询问先把朝向的边界加入 set,使三种情况得到统一,更好地避免了分类讨论。

worse code:

By cxm1024

#include<bits/stdc++.h>
using namespace std;
int xx[200010],yy[200010],tmpx[200010],tmpy[200010],cntx=0,cnty=0;
vector<int> xt[200010],yt[200010];
signed main() {
	int h,w,r,c,n,q;
	cin>>h>>w>>r>>c>>n;
	for(int i=1;i<=n;i++) {
		cin>>xx[i]>>yy[i];
		tmpx[++cntx]=xx[i],tmpy[++cnty]=yy[i];
	}
	sort(tmpx+1,tmpx+cntx+1);
	cntx=unique(tmpx+1,tmpx+cntx+1)-tmpx-1;
	sort(tmpy+1,tmpy+cnty+1);
	cnty=unique(tmpy+1,tmpy+cnty+1)-tmpy-1;
	for(int i=1;i<=n;i++) {
		xt[lower_bound(tmpx+1,tmpx+cntx+1,xx[i])-tmpx].push_back(yy[i]);
		yt[lower_bound(tmpy+1,tmpy+cnty+1,yy[i])-tmpy].push_back(xx[i]);
	}
	for(int i=1;i<=cntx;i++)
		sort(xt[i].begin(),xt[i].end());
	for(int i=1;i<=cnty;i++)
		sort(yt[i].begin(),yt[i].end());
	cin>>q;
	while(q--) {
		char op;
		int len;
		cin>>op>>len;
		int nowx=lower_bound(tmpx+1,tmpx+cntx+1,r)-tmpx;
		if(tmpx[nowx]!=r) nowx=0;
		int nowy=lower_bound(tmpy+1,tmpy+cnty+1,c)-tmpy;
		if(tmpy[nowy]!=c) nowy=0;
		if(op=='U') {
			auto tmp=lower_bound(yt[nowy].begin(),yt[nowy].end(),r);
			if(tmp==yt[nowy].begin()) r=max(1,r-len);
			else {
				tmp--;
				r=max(r-len,(*tmp)+1);
			}
		}
		else if(op=='D') {
			auto tmp=lower_bound(yt[nowy].begin(),yt[nowy].end(),r);
			if(tmp==yt[nowy].end()) r=min(r+len,h);
			else r=min(r+len,(*tmp)-1);
		}
		else if(op=='L') {
			auto tmp=lower_bound(xt[nowx].begin(),xt[nowx].end(),c);
			if(tmp==xt[nowx].begin()) c=max(1,c-len);
			else {
				tmp--;
				c=max(c-len,(*tmp)+1);
			}
		}
		else {
			auto tmp=lower_bound(xt[nowx].begin(),xt[nowx].end(),c);
			if(tmp==xt[nowx].end()) c=min(w,c+len);
			else c=min(c+len,(*tmp)-1);
		}
		cout<<r<<" "<<c<<endl;
	}
	return 0;
}

这份代码直接使用离散化后的 vector 数组来存储障碍,且用了过多分类讨论,实现繁琐。

改进版本:

By cxm1024

#include<bits/stdc++.h>
using namespace std;
int x[200010],y[200010],cntx=0,cnty=0;
map<int,set<int> > xt,yt;
signed main() {
	int h,w,r,c,n,q;
	cin>>h>>w>>r>>c>>n;
	for(int i=1;i<=n;i++)
		cin>>x[i]>>y[i];
	for(int i=1;i<=n;i++) {
		xt[x[i]].insert(y[i]);
		yt[y[i]].insert(x[i]);
	}
	cin>>q;
	while(q--) {
		char op;
		int len;
		cin>>op>>len;
		if(op=='U') {
			auto tmp=yt[c].lower_bound(r);
			if(tmp==yt[c].begin()) r=max(1,r-len);
			else {
				tmp--;
				r=max(r-len,(*tmp)+1);
			}
		}
		else if(op=='D') {
			auto tmp=yt[c].lower_bound(r);
			if(tmp==yt[c].end()) r=min(r+len,h);
			else r=min(r+len,(*tmp)-1);
		}
		else if(op=='L') {
			auto tmp=xt[r].lower_bound(c);
			if(tmp==xt[r].begin()) c=max(1,c-len);
			else {
				tmp--;
				c=max(c-len,(*tmp)+1);
			}
		}
		else {
			auto tmp=xt[r].lower_bound(c);
			if(tmp==xt[r].end()) c=min(w,c+len);
			else c=min(c+len,(*tmp)-1);
		}
		cout<<r<<" "<<c<<endl;
	}
	return 0;
}

wrong code:

By MMNMM(TLE)

#include <bits/extc++.h>

template<typename T, typename S>
std::ostream& operator<<(std::ostream& os, const std::pair<T, S>& p){
    os << p.first << " " << p.second;
    return os;
}

int main() {
    using namespace std;
    long H, W;
    cin >> H >> W;
    pair<long, long> now{};
    cin >> now.first >> now.second;

    unsigned long N;
    cin >> N;
    unordered_map<long, set<long>> vertical, horizontal;
    for(unsigned long i{}; i < N; ++i){
        long x, y;
        cin >> x >> y;
        vertical[x].insert(y);
        horizontal[y].insert(x);
    }

    unsigned long Q;
    cin >> Q;
    for(unsigned long i{}; i < Q; ++i){
        char d;
        long l;
        cin >> d >> l;
        switch(d){
            case 'L':
                if(vertical.count(now.first)){
                    const auto walls{vertical[now.first]};
                    const auto next_wall{walls.upper_bound(now.second)};
                    if(next_wall == begin(walls))now.second -= l;
                    else now.second = max(now.second - l, *prev(next_wall) + 1);
                }else now.second -= l;
                break;
            case 'R':
                if(vertical.count(now.first)){
                    const auto walls{vertical[now.first]};
                    const auto next_wall{walls.lower_bound(now.second)};
                    if(next_wall == end(walls))now.second += l;
                    else now.second = min(now.second + l, *next_wall - 1);
                }else now.second += l;
                break;
            case 'U':
                if(horizontal.count(now.second)){
                    const auto walls{horizontal[now.second]};
                    const auto next_wall{walls.upper_bound(now.first)};
                    if(next_wall == begin(walls))now.first -= l;
                    else now.first = max(now.first - l, *prev(next_wall) + 1);
                }else now.first -= l;
                break;
            case 'D':
                if(horizontal.count(now.second)){
                    const auto walls{horizontal[now.second]};
                    const auto next_wall{walls.lower_bound(now.first)};
                    if(next_wall == end(walls))now.first += l;
                    else now.first = min(now.first + l, *next_wall - 1);
                }else now.first += l;
                break;
        }
        now.first = clamp(now.first, 1L, H);
        now.second = clamp(now.second, 1L, W);
        cout << now << endl;
    }
    return 0;
}

这份代码 TLE 的原因在于第 35、43、51、59 行为了简化代码将 set 复制了一份,而 set 的直接复制是 \(O(n)\) 的,所以导致超时。如果要复制的话,可以选择按引用赋值,即将 const auto walls{vertical[now.first]}; 改为 const auto &walls=vertical[now.first]; 便不会超时。该语句相当于指向了原 set 而不会重新复制一份。

选自《Effective C++》:

The meaning of passing an object by value is defined by the copy constructor of that object's class. This can make pass-by-value an extremely expensive operation.

E. Notebook

题意:你需要维护一个序列,支持:push_backpop_back、记录一个新版本、回退版本。

首先看到这题,第一反应想到用可持久化的数据结构来维护,但对于这道题而言显然过于麻烦了。于是想到可以通过依赖树,将可持久化问题转为非可持久化来做,建图跑 DFS 即可。

By cxm1024

#include<bits/stdc++.h>
using namespace std;
map<int,int> m;
int a[500010];
pair<string,int> ask[500010];
vector<int> now;
vector<pair<int,pair<string,int> > > e[500010];
int ans[500010];
void dfs(int x) {
	if(now.empty()) ans[x]=-1;
	else ans[x]=now.back();
	for(auto v:e[x]) {
		int flag=-1;
		if(v.second.first=="ADD")
			now.push_back(v.second.second);
		else if(v.second.first=="DELETE")
			if(!now.empty())
				flag=now.back(),now.pop_back();
		dfs(v.first);
		if(v.second.first=="ADD")
			now.pop_back();
		else if(v.second.first=="DELETE")
			if(flag!=-1)
				now.push_back(flag);
	}
}
signed main() {
	ios::sync_with_stdio(false);
	int n,tot=0;
	cin>>n;
	for(int i=1;i<=n;i++) {
		a[i]=++tot;
		cin>>ask[i].first;
		if(ask[i].first!="DELETE")
			cin>>ask[i].second;
		if(ask[i].first=="LOAD")
			e[m[ask[i].second]].push_back({a[i],{"",0}});
		else e[a[i-1]].push_back({a[i],ask[i]});
		if(ask[i].first=="SAVE")
			m[ask[i].second]=a[i];
	}
	dfs(0);
	for(int i=1;i<=n;i++)
		cout<<ans[i]<<" ";
	cout<<endl;
	return 0;
}

然而这个做法仍然不够简洁。注意到由于只需要支持 push_backpop_back 这两个较为基础的操作,我们可以使用链表维护。这里使用反向链表,并维护当前的末尾指针,则每次生成新版本只需要记录当前的末尾即可拉出来一整条链。push_back 只需要增加一个元素作为新末尾并指向原末尾;pop_back 则不需要真的删除元素,单纯把当前末尾指针回退一格即可。显然时空复杂度是可以接受的。

By tourist

#include <bits/stdc++.h>

using namespace std;

#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  int tt;
  cin >> tt;
  map<int, int> note;
  vector<pair<int, int>> all(1, make_pair(0, -1));
  int at = 0;
  for (int qq = 0; qq < tt; qq++) {
    string op;
    cin >> op;
    if (op == "ADD") {
      int num;
      cin >> num;
      all.emplace_back(at, num);
      at = (int) all.size() - 1;
    }
    if (op == "DELETE") {
      at = all[at].first;
    }
    if (op == "SAVE") {
      int num;
      cin >> num;
      note[num] = at;
    }
    if (op == "LOAD") {
      int num;
      cin >> num;
      at = note[num];
    }
    cout << all[at].second << " \n"[qq == tt - 1];
  }
  return 0;
}

维护 notebook 也可以不用 map 而使用哈希:

做法1参考(By ygussany

做法2参考(By rainboy

时间复杂度上可以优化掉一个 \(\log\),思路,没有本质区别。

这道题本质上是维护一个可持久化栈,经典的做法就是用链表维护。我们也可以将其封装起来,作为一个专门的数据结构:

By sapphire15

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

#define rep(i,n) for(int i = 0; i < int(n); i++)
#define per(i,n) for(int i = (n) - 1; 0 <= i; i--)
#define rep2(i,l,r) for(int i = (l); i < int(r); i++)
#define per2(i,l,r) for(int i = (r) - 1; int(l) <= i; i--)
#define MM << " " <<
#define pb push_back
#define eb emplace_back
#define all(x) begin(x), end(x)
#define rall(x) rbegin(x), rend(x)
#define sz(x) (int)x.size()

template <typename T>
void print(const vector<T> &v, T x = 0) {
    int n = v.size();
    for (int i = 0; i < n; i++) cout << v[i] + x << (i == n - 1 ? '\n' : ' ');
    if (v.empty()) cout << '\n';
}

using ll = long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;

template <typename T>
bool chmax(T &x, const T &y) {
    return (x < y) ? (x = y, true) : false;
}

template <typename T>
bool chmin(T &x, const T &y) {
    return (x > y) ? (x = y, true) : false;
}


template<class T>
class PersistentStack {
    private:

    class PSNode;
    using node_ptr = std::shared_ptr<PSNode>;

    struct PSNode {
        T _val;
        node_ptr _next;
        PSNode() : _next(nullptr) {}
        PSNode(T x, node_ptr p)
        : _val(x), _next(p) {}
    };

    node_ptr _target;
    std::size_t _size;

    PersistentStack(node_ptr p, std::size_t sz)
    : _target(p), _size(sz) {}

    PersistentStack reverse() {
        PersistentStack now = *this;
        PersistentStack ret;
        while(!now.empty()) {
            ret = ret.push(now.top());
            now = now.pop();
        }
        return ret;
    }

    // friend PersistentQueue<T>;

    public:

    PersistentStack() : _size(0) {
        _target = std::make_shared<PSNode>();
    }

    bool empty() const {
        return _target->_next == nullptr;
    }

    T& top() const {
        assert(!empty());
        return _target->_val;
    }

    size_t size() const noexcept {
        return _size;
    }

    PersistentStack pop() const {
        assert(!empty());
        return PersistentStack(_target->_next, _size - 1);
    }

    PersistentStack push(const T x) const {
        node_ptr ret_p = std::make_shared<PSNode>(x, _target);
        return PersistentStack(ret_p, _size + 1);
    }
};

int main() {
    ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    unordered_map<int, PersistentStack<int>> mp;
    PersistentStack<int> a;
    int q; cin >> q;
    vector<int> ans(q);
    rep(i,q) {
        string s; cin >> s;
        if(s == "ADD") {
            int x; cin >> x;
            a = a.push(x);
        } else if(s == "DELETE") {
            if(!a.empty()) a = a.pop();
        } else if(s == "SAVE") {
            int x; cin >> x;
            mp[x] = a;
        } else if(s == "LOAD") {
            int x; cin >> x;
            a = mp[x];
        }
        if(a.empty()) ans[i] = -1;
        else ans[i] = a.top();
    }
    print(ans);
}
posted @ 2023-03-01 17:27  曹轩鸣  阅读(24)  评论(0编辑  收藏  举报