牛客多校H题题解

链接:[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 ( 1n2×105,105𝑥,𝑦105)(1n2×105,105x,y105), —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]];

每次插入一个二维前缀和的时候,通过mapO(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;
  • 那为什么要反向排列呢?因为反向排列是从后面的元素开始逆推,如果是正向开始,貌似也行哦,只需要重复该步骤就可以了,这里的处理是真的优雅!!!!

posted @   -风间琉璃-  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示