数据结构 21 问

基本数据结构基本功,其中的部分题目模拟较大,注意细节。

主要包括队列,栈,链表,堆,trie,树,表达式问题,单调性问题,基本算法综合,哈希,KMP

供后期复习的打卡,刷题,训练的代码。

注:对于一些复杂的推导单独写题解,其他的就简单写写思路。


双端队列,数组队列

AcWing 132. 小组队列

#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1010, M = 1000010;
int n;
queue<int> q[N];
int st[M];

int ith[N], cnt = 0, hh = 1;

void clear(queue<int> &q)
{
	queue<int> et;
	swap(et,q);
}

int main()
{
	int t, T = 1;
	while(cin >> t && t)
	{
	    hh = 1, cnt = 0;
	    
	    for(int i = 1; i <= t; i ++ )
	    {
	    	
	        int m; cin >> m;
	        while(m -- )
	        {
	            int x; cin >> x;
	            st[x] = i;
	        }
	    }
	    
	    printf("Scenario #%d\n", T ++ );
	    string op;
	    while(cin >> op)
	    {
	        if(op == "STOP") break;
	        
	        if(op == "ENQUEUE")
	        {
	            int x; cin >> x;
	            if(q[st[x]].size() == 0) ith[ ++ cnt] = st[x];
	            q[st[x]].push(x);
	        }
	        else
	        {
	            bool flag = false;
	            for(int i = hh; i <= cnt && !flag; i ++ )
	            {
	                int j = ith[i];
	                if(q[j].size())
	                {
	                    cout << q[j].front() << endl; q[j].pop();
	                    if(q[j].size() == 0) hh ++ ;
	                    flag = true;
	                }
	            }
	        }
	    }
	    puts("");
	    
	    for(int i = 1; i <= t; i ++ )
	        while(!q[i].empty()) q[i].pop(); 
	}
	
	return 0;
}

双栈,前缀和

AcWing 128. 编辑器

#include <iostream>
#include <cstring>
#include <stack>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10;
stack<int> l, r;
int s[N], f[N];

int main()
{
    memset(f, -0x3f, sizeof f);
    int T; cin >> T;
    while(T -- )
    {
        char op; cin >> op;
        if(op == 'I')
        {
            int x; cin >> x;
            l.push(x);
            int n = l.size();
            s[n] = s[n - 1] + x;
            f[n] = max(f[n - 1], s[n]);
        }
        else if(op == 'L')
        {
            if(l.size()) r.push(l.top()), l.pop();
        }
        else if(op == 'R')
        {
            if(r.size()) l.push(r.top()), r.pop();
            int n = l.size();
            s[n] = s[n - 1] + l.top();
            f[n] = max(f[n - 1], s[n]);
        }
        else if(op == 'Q')
        {
            int k; cin >> k;
            cout << f[k] << endl;
        }
        else
        {
            if(l.size()) l.pop();
        }
    }
    
    return 0;
}

dfs

千万不要误以为是全排列,全排列中的一些排列在进出栈中是不合法的。

AcWing 129. 火车进栈

#include <iostream>
#include <vector>
#include <stack>
using namespace std;
vector<int> path;
stack<int> p;
int n;

int cnt = 0;
void dfs(int u)
{
    if(cnt == 20) return;
    if(path.size() == n)
    {
        for(int i = 0; i < path.size(); i ++ )
            cout << path[i];
        puts(""); cnt ++ ;
        return;
    }
    
    if(p.size())
    {
        path.push_back(p.top());
        p.pop();
        dfs(u);
        p.push(path.back());
        path.pop_back();
    }
    if(u <= n)
    {
        p.push(u);
        dfs(u + 1);
        p.pop();
    }
}

int main()
{
    cin >> n;
    
    dfs(1);
    
    return 0;
}

简单的双栈同步模拟(一个存min值,一个存数值)

AcWing 41. 包含min函数的栈

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> s;
    stack<int> imin;

    MinStack() {

    }
    
    void push(int x) {
        s.push(x);
        if(imin.empty() || imin.top() >= x) imin.push(x);
    }
    
    void pop() {
        if(s.top() == imin.top()) imin.pop();
        s.pop();
    }
    
    int top() {
        return s.top();
    }
    
    int getMin() {
        return imin.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

将原序列字段和转换为前缀和数组的相减;

得到\(s_i - s_{j-1}\),每次遍历i的位置,然后在m+1长度的区间内找出最小的\(s_j\)使得字段和最大。

这一性质与单调队列的滑动窗口性质相同。

AcWing 135. 最大子序和

#include <iostream>
#include <deque>
using namespace std;
const int N = 300010;
int a[N], s[N];

deque<int> q;

int main()
{
    int n, m; cin >> n >> m;
    for(int i = 1; i <= n; i ++ )
        cin >> a[i], s[i] = s[i - 1] + a[i];

    int res = -2e9;
    q.push_back(0);
    for(int i = 1; i <= n; i ++ )
    {
        if(q.size() && i - q.front() > m) q.pop_front();
        res = max(res ,s[i] - s[q.front()]);

        while(q.size() && s[q.back()] >= s[i]) q.pop_back();
        q.push_back(i);
    }
    cout << res;

    return 0;
}

首先找出矩形的性质。

根据数据范围可知,四重循环的暴力是会爆的。

那么我们可以以每一行为基准线,把每一行转换为直方图中的最大矩形问题,即可。

附上两题的代码。

131. 直方图中最大的矩形
152. 城市游戏

#include <iostream>
using namespace std;
typedef long long ll;
const int N = 100010;
ll a[N];
ll l[N], r[N], s[N], top = 0;

int main()
{
    int n;
    while(cin >> n && n)
    {
        for(int i = 1; i <= n; i ++ ) scanf("%lld", &a[i]);
        a[0] = -1, a[n + 1] = -1;
        
        top = 0; s[top] = 0;
        for(int i = 1; i <= n; i ++ )
        {
            while(a[s[top]] >= a[i]) top -- ; // 跑到不能再拓宽为止
            l[i] = s[top];
            s[ ++ top ] = i;
        }
        
        top = 0; s[top] = n + 1;
        for(int i = n; i ; i -- )
        {
            while(a[s[top]] >= a[i]) top -- ;
            r[i] = s[top];
            s[ ++ top ] = i;
        }
        
        ll res = 0;
        for(int i = 1; i <= n; i ++ )
            res = max(res, (r[i] - l[i] - 1) * a[i]);
        printf("%lld\n", res);
    }
	return 0;
}
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
int g[N][N];
int l[N], r[N]; //定义i点的左右最远距离
int s[N], top = 0;

void init(int a[])
{
    top = 0;
    s[top] = 0;
    for(int j = 1; j <= m; j ++ )
    {
        while(a[s[top]] >= a[j]) top -- ;
        l[j] = s[top] + 1;
        s[ ++ top] = j;
    }
    
    top = 0;
    s[top] = m + 1;
    for(int j = m; j; j --)
    {
        while(a[s[top]] >= a[j]) top -- ;
        r[j] = s[top] - 1;
        s[ ++ top] = j;
    }
}

int solve(int h[]) //分解成行内的直方图矩形问题
{
    init(h);
    
    int res = 0;
    for(int j = 1; j <= m; j ++ ) // r是正向的,要镜像一下
        res = max(res, (r[j] - l[j] + 1) * h[j]);
        
    return res;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i ++ )
    {
        g[i][0] = g[i][m + 1] = -1;
        for(int j = 1; j <= m; j ++ )
        {
            char t; cin >> t;
            if(t == 'F') g[i][j] = g[i - 1][j] + 1;
            else g[i][j] = 0;
        }
    }
    
    int res = 0;
    for(int i = 1; i <= n; i ++ )
        res = max(res, solve(g[i]));
    cout << res * 3;
    
    return 0;
}

题意十分简单,没有复杂的性质。
考察对数据结构的应用和代码的基本功。

下面给出两种方法。

AcWing 136. 邻值查找

100 pts
平衡树(set)

#include <iostream>
#include <cmath>
#include <set>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n;
set<PII> s;

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ )
    {
        int x; cin >> x;
        s.insert({x, i});
        
        set<PII>::iterator it = s.find({x, i});
        
        if(i == 1) continue;
        PII res = {2e9, -1};
        
        if( ++ it != s.end())
            res = {(*it).first - x, (*it).second};
        
        it -- ;
        if(it -- != s.begin() && res.first >= x - (*it).first)
            res = {x - (*it).first, (*it).second};
        
        cout << res.first << ' ' << res.second << endl;
    }
    return 0;
}

链表

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;
typedef pair<ll, int> PII;
int l[N], r[N], p[N]; //邻接表
PII a[N]; int n;

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ ) cin >> a[i].first, a[i].second = i;
    sort(a + 1, a + n + 1);
    
    a[0].first = -4e9, a[n + 1].first = 4e9; // 哨兵要开大, 超过两倍的(2 * 2e9)
    for(int i = 1; i <= n; i ++ )
    {
        l[i] = i - 1 , r[i] = i + 1;
        p[a[i].second] = i;
    }
    
    PII res[N];
    for(int i = n; i ; i -- ) // 倒序,因为每次查询的是i往前的数
    {
        int j = p[i]; int pre = l[j], ne = r[j];
        ll b = (ll)a[j].first - a[pre].first;
        ll c = (ll)a[ne].first - a[j].first;
        
        if(b <= c) res[i] = {b, a[pre].second};
        else res[i] = {c, a[ne].second};
        l[ne] = pre, r[pre] = ne; // 删除该节点
    }
    
    for(int i = 2; i <= n; i ++ ) cout << res[i].first << ' ' << res[i].second << endl;
    
    return 0;
}

首先分析性质:

当$ 0 < i < j < k , a[k] < a[i] < a[j],那么i,j一定不能在同一个栈中$
这一点的证明建议自推一遍。

那么这么多的关系,只能转化为图论来解决。因为存在两个阵营,且阵营内部没有矛盾,所以可以判二分图,证明是否可以双栈排序。

然后就是优先级问题。网上很多的代码都是有缺陷的,这个是100 pts

AcWing 153. 双栈排序

#include <iostream>
#include <cstring>
#include <stack>
using namespace std;
const int N = 1010;

int f[N]; // 表示从i到n中的最小值
int a[N], n;
stack<int> s[3];

bool g[N][N];
int color[N];
bool dfs(int u, int c)
{
    color[u] = c;
    for(int i = 1; i <= n; i ++ )
        if(g[u][i])
        {
            if(color[i] == c) return false;
            if(color[i] == -1 && !dfs(i, 3 - c)) return false;
        }
        
    return true;
}

int ith = 1;
// 分配站栈中的操作(判断,修改)
bool check(int k)
{
    return s[k].size() && s[k].top() == ith;
}

void pop(int k)
{
    ith ++ ;
    s[k].pop();
    char op = k == 1 ? 'b' : 'd';
    cout << op << ' ';
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ ) cin >> a[i];
    
    f[n + 1] = 2e9;
    for(int i = n; i >= 0; i -- ) f[i] = min(f[i + 1], a[i]);
    
    memset(g, false, sizeof g);
    for(int i = 1; i <= n; i ++ )
        for(int j = i + 1; j <= n; j ++ )
            if(a[i] < a[j] && f[j + 1] < a[i]) // 表示存在i,j,k这个情况
                g[i][j] = g[j][i] = true;
    
    bool is_success = true;
    memset(color, -1, sizeof color);
    for(int i = 1; i <= n; i ++ )
        if(color[i] == -1 && !dfs(i, 1))
        {
            is_success = false;
            break;
        }
    
    if(!is_success)
    {
        puts("0");
        return 0;
    }
    
    // 方案查询(重点在于优先级问题)
    for(int i = 1; i <= n; i ++ )
    {
        int c = color[i], x = a[i];
        
        if(c == 2) // 进二号栈之前必须先尽可能的出一号栈
            while(check(1)) pop(1);
            
        // 新进的数字不满足该栈的单调递减性,但是必须塞入,所以先弹出s1,s2
        while(s[c].size() && s[c].top() < x)
        {
            if(!check(c)) pop(3 - c);
            else pop(c);
        }
        
        if(c == 2) // 同上
            while(check(1)) pop(1);
        s[c].push(x); 
        char op = c == 1 ? 'a' : 'c';
        cout << op << ' ';
    }
    
    // 剩下的目标就是弹出所有数字
    while(s[1].size())
    {
        if(!check(1)) pop(2);
        else pop(1);
    }
    while(s[2].size()) pop(2);
    
    return 0;
}

超棒的题目!!!

这道题综合考察了性质和单调性,队列的应用就是小事了,主要是从局部到整体考虑。

这个性质藏的很隐蔽,具体写了题解

AcWing 133. 蚯蚓

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
typedef long long ll;
ll n, m, q, u, v, t;
ll a[N];
queue<ll> q1, q2, q3;

int get(int dt)
{
    ll a = -1, b = -1, c = -1;
    if(q1.size()) a = q1.front() + q * dt;
    if(q2.size()) b = q2.front() + q * dt;
    if(q3.size()) c = q3.front() + q * dt;
    
    ll res = max(a, max(b, c));
    
    if(res == a) q1.pop();
    else if(res == b) q2.pop();
    else if(res == c) q3.pop();
    return res;
}

int main()
{
    cin >> n >> m >> q >> u >> v >> t;
    for(int i = 1; i <= n; i ++ ) cin >> a[i];
    
    sort(a + 1, a + n + 1), reverse(a + 1, a + n + 1);
    for(int i = 1; i <= n; i ++ ) q1.push(a[i]);
    
    for(int i = 1; i <= m; i ++ )
    {
        ll x = get(i - 1);
        if(i % t == 0) cout << x << ' ';
        
        ll a = x * u / v; ll b = x - a;
        q2.push(a - i * q);
        q3.push(b - i * q);
    }
    cout << endl;
    
    for(int i = 1; i <= n + m; i ++ )
    {
        ll x = get(m);
        if(i % t == 0) cout << x << ' ';
    }
    
    return 0;
}

kmp模板题,纯字符串匹配。

AcWing 831. KMP字符串

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
char p[N], s[N * 10];
int n, m;

int ne[N]; // 最大前缀和后缀的匹配表

int main()
{
    cin >> n >> p + 1 >> m >> s + 1;
    
    for(int i = 2, j = 0; i <= n; i ++ )
    {
        while(j && p[i] != p[j + 1]) j = ne[j];
        if(p[i] == p[j + 1]) j ++ ;
        ne[i] = j;
    }
    
    for(int i = 1, j = 0; i <= m; i ++ )
    {
        while(j && s[i] != p[j + 1]) j = ne[j];
        if(s[i] == p[j + 1]) j ++ ;
        if(j == n)
        {
            cout << i - n << ' ';
            j = ne[j];
        }
    }
    
    return 0;
}

AcWing 141. 周期

kmp变式题,需证明循环节与主串的关系

#include <iostream>
#include <cstring>
using namespace std;
const int N = 1000010;
int ne[N];
char s[N];
int n;

int main()
{
    int T = 1;
    while(cin >> n && n)
    {
        printf("Test case #%d\n", T ++ );
        
        memset(ne, 0, sizeof ne);
        cin >> s + 1;
        
        for(int i = 2, j = 0; i <= n; i ++ )
        {
            while(j && s[i] != s[j + 1]) j = ne[j];
            if(s[i] == s[j + 1]) j ++ ;
            ne[i] = j;
        }
        
        for(int i = 2; i <= n; i ++ )
        {
            int len = i - ne[i];
            if(i % len == 0 && i != len) cout << i << ' ' << i / len << endl;
        }
        
        puts("");
        
    }
    return 0;
}

AcWing 144. 最长异或值路径

dfstrie

先转换异或结果的求法,转换为每个点到根节点的路径长度,然后用trie去查询能够异或出的最大值

#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int n, idx = 0;
int h[N], e[N * 2], ne[N * 2], w[N * 2];
int dist[N], son[N * 32][2];

void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++ ;
}

void dfs(int u, int fa, int k)
{
    dist[u] = k;
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == fa) continue;
        dfs(j, u, k ^ w[i]);
    }
}

void insert(int x)
{
    int p = 0;
    for(int i = 31; i >= 0; i -- )
    {
        int r = x >> i & 1;
        if(!son[p][r]) son[p][r] = ++ idx;
        p = son[p][r];
    }
}

int query(int x)
{
    int res = 0, p = 0;
    for(int i = 31; i >= 0; i -- )
    {
        int r = x >> i & 1;
        if(son[p][!r])
        {
             p = son[p][!r];
             res = res * 2 + !r;
        }
        else
        {
             p = son[p][r];
             res = res * 2 + r;
        }
    }
    
    return res;
}

int main()
{
    memset(h, -1, sizeof h);
    
    cin >> n;
    for(int i = 1; i < n; i ++ )
    {
        int a, b, c; cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    
    dfs(0, -1, 0);
    idx = 0;
    
    for(int i = 0; i < n; i ++ ) insert(dist[i]);
    int res = 0;
    for(int i = 0; i < n; i ++ )
    {
        int t = query(dist[i]);
        res = max(res, t ^ dist[i]);
    }
    
    cout << res;
    return 0;
}

AcWing 160. 匹配统计

比较吊的kmp,也可以用hash做。

#include <iostream>
#include <cstring>
using namespace std;
const int N = 200010; 
int n, m, q;
char a[N], b[N];
int cnt[N], ne[N];

int main()
{
    cin >> n >> m >> q;
    cin >> a + 1 >> b + 1;
    
    for(int i = 2, j = 0; i <= m; i ++ )
    {
        while(j != 0 && b[i] != b[j + 1]) j = ne[j];
        if(b[i] == b[j + 1]) j ++ ;
        ne[i] = j;
    }
    
    for(int i = 1, j = 0; i <= n; i ++ )
    {
        while(j != 0 && a[i] != b[j + 1]) j = ne[j];
        if(a[i] == b[j + 1]) j ++ ;
        cnt[j] ++ ;
    }
    
    for(int i = m; i >= 1; i -- ) cnt[ne[i]] += cnt[i];
    
    while(q -- )
    {
        int x; cin >> x;
        cout << cnt[x] - cnt[x + 1] << endl;
    }
    
    return 0;
}

AcWing 145. 超市

两种做法!

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 10010;
typedef pair<int, int> PII;
PII a[N];

int main()
{
    int n;
    while(cin >> n)
    {
        for(int i = 0; i < n; i ++ ) cin >> a[i].second >> a[i].first;
        sort(a, a + n);
        
        priority_queue<int, vector<int>, greater<int> > q;
        for(int i = 0; i < n; i ++ )
        {
            q.push(a[i].second);
            if(a[i].first < q.size()) q.pop();
        }
        
        int res = 0;
        while(q.size()) res += q.top(), q.pop();
        cout << res << endl;
    }
    
    return 0;
}

再给一个并查集做法:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10010;
typedef pair<int, int> PII;
PII a[N];
int p[N], n;

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    int n;
    while(cin >> n)
    {
        for(int i = 1; i < N; i ++ ) p[i] = i;
        
        for(int i = 0; i < n; i ++ ) cin >> a[i].first >> a[i].second;
        sort(a, a + n), reverse(a, a + n);
        
        int res = 0;
        for(int i = 0; i < n; i ++ )
        {
            int j = a[i].second;
            int r = find(a[i].second);
            if(r)
            {
                res += a[i].first;
                p[r] = r - 1;
            }
        }
        cout << res << endl;
    }
    return 0;
}

并查集

AcWing237. 程序自动分析

离线做法,并查集判断关系是否成立。

#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
const int N = 200010;
unordered_map<int, int> s; int idx = 0;
typedef pair<int, int> PII;
vector<PII> f;

int p[N];
int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    int T; cin >> T;
    while(T -- )
    {
        int n; cin >> n;
        idx = 0;
        for(int i = 1; i <= 2 * n; i ++ ) p[i] = i;
        f.clear(), s.clear();
        
        for(int i = 0; i < n; i ++ )
        {
            int x, y, e; cin >> x >> y >> e;
            if(!s.count(x)) s[x] = ++ idx;
            if(!s.count(y)) s[y] = ++ idx;
            x = s[x], y = s[y];
            
            if(!e) f.push_back({x, y});
            else
            {
                int a = find(x), b = find(y);
                p[a] = b;
            }
        }
        
        bool flag = true;
        for(int i = 0; i < f.size(); i ++ )
        {
            int a = f[i].first, b = f[i].second;
            if(find(a) == find(b))
            {
                flag = false;
                break;
            }
        }
        
        puts(flag ? "YES" : "NO");
    }
    return 0;
}

AcWing 259. 真正的骗子

并查集和背包的运用

#include <iostream>
#include <cstring>
using namespace std;
const int N = 610, M = 1210;
int n, m, p1, p2;
int p[M], s[M], tot;
int vis[M], a[N], b[N];
int f[N][N];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void merge(int a, int b)
{
    int x = find(a), y = find(b);
    if(x == y) return;
    p[x] = y, s[y] += s[x];
}

void init()
{
    memset(f, 0, sizeof f);
    m = p1 + p2, tot = 0; f[0][0] = 1;
    for(int i = 1; i <= m; i ++ )
    {
        p[i] = i, p[i + m] = i + m;
        s[i] = 1, s[i + m] = 0;
        vis[i] = vis[i + m] = 0;
    }
}

int main()
{
    while(cin >> n >> p1 >> p2 && n + p1 + p2)
    {
        init();
        while(n -- )
        {
            int a0, b0; string s;
            cin >> a0 >> b0 >> s;
            if(s == "yes") merge(a0, b0), merge(a0 + m, b0 + m);
            else merge(a0 + m, b0), merge(a0, b0 + m);
        }
        
        for(int i = 1; i <= m; i ++ )
        {
            int j = find(i);
            if(j != i) continue;
            vis[j] = vis[j + m] = ++ tot;
            a[tot] = s[j], b[tot] = s[j + m];
        }
        
        for(int i = 1; i <= tot; i ++ )
            for(int j = min(a[i], b[i]); j <= p1; ++ j )
            {
                if(j >= a[i]) f[i][j] += f[i - 1][j - a[i]];
                if(j >= b[i]) f[i][j] += f[i - 1][j - b[i]];
            }
        if(f[tot][p1] != 1) puts("no");
        else
        {
            for(int i = tot; i; -- i )
                if(f[i - 1][p1 - a[i]]) p1 -= a[i], a[i] = -1; // 以同类作为神
                else if(f[i - 1][p1 - b[i]]) p1 -= b[i], a[i] = -2;
            
            for(int i = 1; i <= m; i ++ )
            {
                int j = find(i);
                if(j <= m && a[vis[j]] == -1) cout << i << endl;
                else if(j > m && a[vis[j]] == -2) cout << i << endl;
            }
            puts("end");
        }
    }
    return 0;
}

AcWing 242. 一个简单的整数问题

数状数组结合差分,解决区间修改单点查询

#include <iostream>
using namespace std;
typedef long long ll;
const int N = 200010;
int a[N];
int c[N], n, m;

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

int add(int x, int k)
{
    for(int i = x; i <= n; i += lowbit(i)) c[i] += k;
}

int query(int x)
{
    int sum = 0;
    for(int i = x; i; i -= lowbit(i)) sum += c[i];
    return sum;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i ++ ) cin >> a[i];
    for(int i = 1; i <= n; i ++ ) add(i, a[i] - a[i - 1]);
    while(m -- )
    {
        char op; cin >> op;
        if(op == 'C')
        {
            int l, r, k; cin >> l >> r >> k;
            add(l, k), add(r + 1, -k);
        }
        else
        {
            int k; cin >> k;
            cout << query(k) << endl;
        }
    }
}

AcWing 244. 谜一样的牛

数状数组和二分综合

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int tr[N];
int c[N], n;
int h[N];

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

void add(int x, int c)
{
    for(int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int query(int x)
{
    int sum = 0;
    for(int i = x; i; i -= lowbit(i)) sum += tr[i];
    return sum;
}

int main()
{
    cin >> n;
    for(int i = 2; i <= n; i ++ ) cin >> c[i];
    for(int i = 1; i <= n; i ++ ) tr[i] = lowbit(i);
    
    for(int i = n; i; i -- )
    {
        int l = 1, r = n;
        while(l < r)
        {
            int mid = l + r >> 1;
            if(query(mid) <= c[i]) l = mid + 1;
            else r = mid;
        }
        
        add(r, -1);
        h[i] = r;
    }
    for(int i = 1; i <= n; i ++ ) cout << h[i] << endl;
    
    return 0;
}

AcWing 241. 楼兰图腾

数状数组,双向遍历。

#include <iostream>
#include <cstring>
using namespace std;
const int N = 2e5+10;
typedef long long ll;
int n;
int h[N], c[N];

int larger[N], lower[N];

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

void add(int x, int k)
{
    for(int i = x; i <= n; i += lowbit(i)) c[i] += k;
}


int sum(int x)
{
    int res=0;
    for(int i = x; i; i -= lowbit(i))
        res += c[i];
    return res;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ ) cin >> h[i];
    
    for(int i = 1; i <= n; i ++ ) // 从左到右
    {
        int y = h[i];
        larger[i] = sum(n) - sum(y);
        lower[i] = sum(y - 1);
        add(y, 1);
    }
    memset(c, 0, sizeof c);
    
    ll res1 = 0, res2 = 0;
    for(int i = n; i >= 1; i -- ) // 反向遍历并计算
    {
        int y = h[i];
        res1 += (ll) larger[i] * (sum(n) - sum(y));
        res2 += (ll) lower[i] * sum(y - 1);
        add(y, 1);
    }
    
    cout << res1 << ' ' << res2;
    return 0;
}

AcWing 1277. 维护序列

线段树所有操作的练习!

#include <iostream>
using namespace std;
typedef long long ll;
const int N = 4e5 + 10;
int n, p;
int w[N];

struct Node{
    int l[N], r[N];
    int v[N];
    int tmul[N], tadd[N];
    
    void pushup(int u)
    {
        v[u] = (v[u << 1] + v[u << 1 | 1]) % p;
    }
    
    void eval(int u, int add, int mul)
    {
        v[u] = ((ll)v[u] * mul + (ll)(r[u] - l[u] + 1) * add) % p;
        tadd[u] = ((ll)tadd[u] * mul + add) % p;
        tmul[u] = (ll)tmul[u] * mul % p;
    }
    
    void pushdown(int u)
    {
        eval(u << 1, tadd[u], tmul[u]);
        eval(u << 1 | 1, tadd[u], tmul[u]);
        tadd[u] = 0, tmul[u] = 1;
    }
    
    void build(int u, int a, int b)
    {
        if(a == b) l[u] = r[u] = a, v[u] = w[a], tmul[u] = 1;
        else
        {
            l[u] = a, r[u] = b, v[u] = 0, tmul[u] = 1;
            int mid = a + b >> 1;
            build(u << 1, a, mid), build(u << 1 | 1, mid + 1, b);
            pushup(u);
        }
    }
    
    void modify(int u, int a, int b, int add, int mul)
    {
        if(a <= l[u] && r[u] <= b) eval(u, add, mul);
        else
        {
            pushdown(u);
            int mid = l[u] + r[u] >> 1;
            if(a <= mid) modify(u << 1, a, b, add, mul);
            if(b > mid) modify(u << 1 | 1, a, b, add, mul);
            
            pushup(u);
        }
    }
    
    int query(int u, int a, int b)
    {
        if(a <= l[u] && r[u] <= b) return v[u];
        
        pushdown(u);
        
        int mid = l[u] + r[u] >> 1;
        int res = 0;
        if(a <= mid) res = query(u << 1, a, b);
        if(b > mid) res = (res + query(u << 1 | 1, a, b)) % p;
        pushup(u);
        
        return res;
    }
}t;

int main()
{
    cin >> n >> p;
    for(int i = 1; i <= n; i ++ ) cin >> w[i];
    t.build(1, 1, n);
    
    int q; cin >> q;
    while(q -- )
    {
        int op; cin >> op;
        int a, b; cin >> a >> b;
        if(op == 1)
        {
            int c; cin >> c;
            t.modify(1, a, b, 0, c);
        }
        else if(op == 2)
        {
            int c; cin >> c;
            t.modify(1, a, b, c, 1);
        }
        else
        {
            cout << t.query(1, a, b) << endl;
        }
        
        //for(int i = 1; i <= n; i ++ ) cout << t.query(1, i, i) << ' ';
        //cout << endl;
    }
    
    return 0;
}
posted @ 2023-01-28 10:45  Sankano  阅读(37)  评论(0编辑  收藏  举报