牛客多校H題題解

cxjy發表於2024-07-18

連結:[https://ac.nowcoder.com/acm/contest/81597/H]
來源:牛客網

題目描述

Red stands at the coordinate \((0,0)\) of the Cartesian coordinate system. She has a string of instructions: up, down, left, right (where 'right' increases the x-coordinate by \(1\), and 'up' increases the y-coordinate by \(1\)).Now Red wants to select a continuous substring of instructions and execute them. Red hopes that the final execution of the instructions can pass through the coordinate \((x,y)\). She wants to know how many selection options there are.

輸入描述:

The first line contains three integers \(n\), \(x\), and \(y\) ( \(1≤n≤2×10^5\),\(−10^5≤𝑥,𝑦≤10^5\))(\(1≤n≤2×10^5\),\(−10^5≤x,y≤10^ 5\)), —the length of the instruction string and the coordinates Red hopes to pass through.

The second line contains a string of length 𝑛n, consisting of the characters '\(W\)', '\(S\)', '\(A\)', and '\(D\)'. — the four directions: up, down, left, and right, respectively.

輸出描述:

Output one integer representing the number of selection options for the continuous substring.

解題大概思路

  • 該題目我們想的暴力思路大概就是透過構建兩個字首和陣列來,每次透過雜湊表查詢字首和與目標\(x\) (或者\(y\))的對應的字首和是否存在,如果存在,就把該區間存入區間另外一個雜湊表內.這一部分的時間複雜度為\(O(nlog(n))\)級別

  • 然後跑了一遍\(O(n)\)迴圈之後,再遍歷一遍儲存著符合條件的區間的雜湊表,從雜湊表裡面選出符合要求的區間,這裡特別要注意統計區間的時候要考慮清楚,避免重複統計區間


程式碼處理:

特殊判定 起點為(0,0)的情況下:

    if(!x && !y) {
        cout << n * (n + 1) / 2 << nn;
        return;
    }

初始化雜湊表,記錄出現過的區間

    mp[{0, 0}].push_back(0);
    set<pair<int, int>> se;

初始化字首和陣列

    sx[i] = sx[i - 1] + dx[s[i]];
    sy[i] = sy[i - 1] + dy[s[i]];

每次插入一個二維字首和的時候,透過\(map\)\(O(log(n))\) 的時間複雜度來進行查詢是否查詢到了所需要的區間

像我比賽時寫的程式碼進行了三次雜湊查詢,其中有一個雜湊查詢還相互巢狀,時間複雜度大概是\(O((log(n))^2)\)所以TLE了

        auto v = mp[{sx[i] - x, sy[i] - y}];
        //取出 右邊的下標


        if(!v.empty()) {
            //如果找到了對應的一個字首和
            for(auto l: v) {
                // if(l == i) se.insert({l, i});
                if(l + 1 <= i) se.insert({l + 1, i});
                //前面的if判斷其實很多餘
            }
        }

最後再在大的set裡面找出所需要的區間

其實這裡就算不用點陣圖去重也可以的,因為此時雜湊表裡面的區間都是滿足題目意思的區間

  • 所以這裡我感覺直接輸出se.size()就是最佳答案
    for(auto x: se) {
        int l = x.first, r = x.second;
        if(vis[l]) continue;
        vis[l] = 1;
        ans += n - r + 1;
        //如果有效就加上 n-r+1
    }

AC code:

#include<bits/stdc++.h>
#define int long long
#define nn '\n'
using namespace std;
 
const int maxn = 2e5 + 5;
int sx[maxn], sy[maxn]; // 位置從1開始
bitset<maxn> vis;
 
void solve() {
    int n, x, y, ans = 0;
    string s;
    cin >> n >> x >> y >> s;

    if(!x && !y) {
        cout << n * (n + 1) / 2 << nn;
        return;
    }

    //特殊判定 起點為(0,0)的情況下
    s = '0' + s;
    map<char, int> dx, dy;
    dx['D'] = 1, dx['A'] = -1;
    dy['W'] = 1, dy['S'] = -1;
    map<pair<int, int>, vector<int>> mp;

    //初始化雜湊表,記錄出現過的區間
    mp[{0, 0}].push_back(0);
    set<pair<int, int>> se;
    for(int i = 1; i <= n; i++) {

        sx[i] = sx[i - 1] + dx[s[i]];
        sy[i] = sy[i - 1] + dy[s[i]];

        //初始化字首和陣列

        mp[{sx[i], sy[i]}].push_back(i);
        
        //直接這樣插入元素???

        auto v = mp[{sx[i] - x, sy[i] - y}];
        //取出 右邊的下標


        if(!v.empty()) {
            //如果找到了對應的一個字首和
            for(auto l: v) {
                // if(l == i) se.insert({l, i});
                if(l + 1 <= i) se.insert({l + 1, i});
                //前面的if判斷其實很多餘
            }
        }
    }
    // for(auto x: se) {
    //     cout << x.first << ' ' << x.second << nn;
    // }
    set<string> ss;
    //然後現在要對存入區間內的有效區間0進行判斷
    for(auto x: se) {
        int l = x.first, r = x.second;
        if(vis[l]) continue;
        vis[l] = 1;
        ans += n - r + 1;
        //如果有效就加上 n-r+1
    }
    cout << ans << nn;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _ = 1;
    // cin >> _;
    while(_--)
        solve();
    return 0;
}

這裡附上一篇大佬的程式碼,簡潔優雅簡直讓人拜服!

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, a, b, ans;
char c;
pair<int, int> m[1000005];
map<pair<int, int>, int> t;
signed main()
{
	cin >> n >> a >> b;
	if (a == 0 && b == 0) {
		cout << n * (n + 1) / 2;
		return 0;
	}
	for (int i = 1; i <= n; i++) {
		cin >> c;
		if (c == 'A') m[i] = {m[i - 1].first - 1, m[i - 1].second + 0};
		if (c == 'S') m[i] = {m[i - 1].first + 0, m[i - 1].second - 1};
		if (c == 'W') m[i] = {m[i - 1].first + 0, m[i - 1].second + 1};
		if (c == 'D') m[i] = {m[i - 1].first + 1, m[i - 1].second + 0};
	}
	for (int i = n; i > 0; i--) {
		t[{m[i].first - a, m[i].second - b}] = n - i + 1;
		ans += t[{m[i - 1].first, m[i - 1].second}];
	}
	cout <<br ans;
	return 0;
}

那麼這段AC程式碼到底包含著什麼樣的邏輯呢??


1.首先透過設定字首和來統計各個字首字串到達的位置

2.反向進行遍歷,如果此時map裡面存在元素,那麼map內包含的也就是n-i+1(後面計算過的),如果不存在,就是0;
  • 那為什麼要反向排列呢?因為反向排列是從後面的元素開始逆推,如果是正向開始,貌似也行哦,只需要重複該步驟就可以了,這裡的處理是真的優雅!!!!

相關文章