蓝桥小小结

100. 增减序列

给定一个长度为 n 的数列 a1,a2,…,an,每次可以选择一个区间 [l,r],使下标在这个区间内的数都加一或者都减一

求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。

分析

  • 因为终态所有数都一样,所以最后差分数组一定是 b [1] = 一个常数,b [2]-b [n] 都等于 0 (求数列种数的时候也可以使用)

  • 差分数组数组的操作 b [l] += 1,b [r+1] -= 1 的特性可以使正负两个数相消,所以最后差分就只剩同符号的数,此时操作数为 min (pos,neg) //pos 为差分数组中正数和 neg为负数和的绝对值

  • b [1] 的操作次数也就是种类数量。想到数列的值就是 b [1] 的值 操作有对b[1]和b[n+1]等价,eg 假设一开始 b [1] 为 2,abs (pos-neg) 为 3,b [1] 的取值可能为 2,3,4,5,即 abs (pos-neg)+1

方案一 b [1] += 1,b [i+1] -=1;
方案二 b [i] -= (-1),b [n+1] += (-1);
等价

ps.
差分:差分与前缀和相对,可以视为前缀和的逆运算。差分它可以维护多次对序列的一个区间修改一个数。

“区间修改+单点查询”,用差分数组往往不够用。因为差分数组对“区间修改”很高效,但是对“单点查询”并不高效。此时需要用树状数组和线段树来求解

code:

#include<iostream>
#include<string.h>
using namespace std;
const int N = 100010;
#define ll long long 
ll a[N];
ll b[N];
int main(){
    ll n;
    cin >> n;
    for(ll i=1;i<=n;i++){
        cin >> a[i];
        b[i] = a[i] - a[i-1];     // b数组为差分数组
    }
    ll pos=0,neg=0;
    for(ll i=2;i<=n;i++){
        if(b[i] > 0) pos += b[i];
        else neg -= b[i];         // 相当于取绝对值
    }
    ll ans1,ans2;
    ans1 = min(neg,pos) + abs(pos-neg);
    ans2 = abs(pos-neg)+1;
    cout << ans1 << endl << ans2;
}

作者:chen_
链接:https://www.acwing.com/file_system/file/content/whole/index/content/8274441/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1460. 我在哪?

约翰想要知道最小的 K 的值,使得他查看任意连续 K个邮箱序列,他都可以唯一确定这一序列在道路上的位置。

例如,假设沿路的邮箱序列为 ABCDABC 。

约翰不能令 K=3,因为如果他看到了 ABC,则沿路有两个这一连续颜色序列可能所在的位置。

最小可行的 K 的值为 K=4,因为如果他查看任意连续 4 个邮箱,那么可得到的连续颜色序列可以唯一确定他在道路上的位置

分析

最小不重复连续子串

STL哈希+二分查找
***** 使用二分查找只要满足二段性即可,不一定非得要有序

长度具有单调性:
因为越长,子串区分度就越大
如果长度为i不行,那么i-1肯定也不行,反之i行,i+1肯定也行,具有二段性

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>
using namespace std;
int n;
string str;
unordered_set<string> S;
bool check(int mid){
    S.clear();
    for (int i = 0; i + mid - 1 < n; i ++ ){
        string s = str.substr(i, mid);
        if (S.count(s)) return false;
 // 已经存在的s字符串个数大于1
        S.insert(s);
    }
    return true;
}

int main(){
    cin >> n >> str;
    int l = 1, r = n;
    while (l < r){   // 对长度进行二分
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << r << endl;
    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/5509029/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

ps.
字符串哈希: https://www.cnblogs.com/moyujiang/p/11213535.html
后缀数组:https://www.cnblogs.com/zwfymqz/p/8413523.html#_labelTop


1497. 树的遍历

给出树的后序遍历和中序遍历,输出层序遍历

输入样例:

7
2 3 1 5 7 6 4
1 2 3 4 5 6 7

输出样例:

4 1 6 3 5 7 2

code

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>

using namespace std;

const int N = 35;

int n;
int a[N],b[N],p[N];
int l[N],r[N];

int build(int al,int ar,int bl,int br,int d){
    if(al > ar) return 0;
    int val = a[ar];
    int k = p[val];
    l[val] = build(al,al+k-1-bl,bl,k-1,d+1);
    r[val] = build(al+k-bl,ar-1,k+1,br,d+1);
    return val;
}

void bfs(){
    queue<int> q;
    q.push(a[n-1]);
    while(q.size()){
        int t = q.front();
        q.pop();
        cout << t << " ";
        if(l[t]) q.push(l[t]);
        if(r[t]) q.push(r[t]);
    }
}


int main(){
    cin >> n;
    for(int i=0;i<n;i++) cin >> a[i];
    for(int i=0;i<n;i++) cin >> b[i];
    for(int i=0;i<n;i++) p[b[i]] = i;  //记录的是中序遍历点的具体位置
                                       //因为在build过程中需要用长度
    build(0,n-1,0,n-1,0);
    bfs();
    
    return 0;
}

一棵二叉树可由它的中序遍历和后序遍历确定
这是因为:
1.后序遍历的最后一定是该树或子树的根
2.中序遍历根的左右分左子树和右子树

层序遍历是一个bfs的过程

ps. 给出树的后序遍历和中序遍历,输出后序遍历

#include<iostream>
#include<cstring>
using namespace std;
const int N = 10010;
char a[N],b[N];   // a->前序   b->中序
int idx;
void build(int l,int r){
	if(l > r) return;
	int u = strchr(b,a[idx])-b;  //a[idx]在 b数组的下标位置 
	idx++;
	build(l,u-1);     // 左子树区间 
	build(u+1,r);     // 右子树区间
	cout << b[u]; 
} 
int main(){
	scanf("%s%s",&a,&b);
	build(0,strlen(a)-1);
	return 0;
}

2058. 笨拙的手指

每当贝茜将数字转换为一个新的进制并写下结果时,她总是将其中的某一位数字写错。

例如,如果她将数字 14转换为二进制数,那么正确的结果应为 1110,但她可能会写下 0110 或 1111。

贝茜不会额外添加或删除数字,但是可能会由于写错数字的原因,写下包含前导 0的数字。

给定贝茜将数字 N转换为二进制数字以及三进制数字的结果(即各其中一位是错误的),请确定 N 的正确初始值(十进制表示)。

分析

如何在不同进制之间转换数字
可以用秦九韶算法将b进制转换为十进制

// 将b进制的数转化成十进制
int get(string s, int b)
{
    int res = 0;
    // 秦九韶算法:每次将前面的数向高位移动一位,即*b,然后加上当前个位数,即+c
    for (auto c: s)
        res = res * b + c - '0';
    return res;
}

code

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>  

using namespace std;

int base(string s,int b){
    int res = 0;
    for(auto x:s){  //枚举s的每一位
        res = res*b+x-'0';
    }
    return res;
}

int main(){
    string x,y;
    cin >> x >> y;
    unordered_set<int> hash;
    for(int i=0;i<x.size();i++){
        string s = x;
        s[i] ^= 1;
        if(s.size()>1 && s[0]=='0') continue;
        // 因为原先的字符串s的首个位置不可能为0
        hash.insert(base(s,2));
    }
    
    for(int i=0;i<y.size();i++){
        for(int j=0;j<3;j++){
            if(y[i]-'0'!=j){
                string s = y;
                s[i] = j+'0';
                if(s.size()>1 && s[0]=='0') continue;
                int n=base(s,3);
                if(hash.count(n))
                    cout << n << endl;
            }
        }
    }
    
    return 0;
}

3555. 二叉树

给定一个 n 个结点(编号 1∼n)构成的二叉树,其根结点为 1号点。

进行 m次询问,每次询问两个结点之间的最短路径长度。

树中所有边长均为 1。

分析

LCA问题

对于任意两个结点, 它们之间的最短距离会等于两个结点与它们的最近公共祖先之间的深度差之和

先把两个结点调整到同一深度, 然后同时往上走, 直到两个结点相等。
要完成这些, 除了记录左右儿子, 还要记录父节点以及各结点深度。

  • 朴素做法
    维护x1为较深的那个,x1往上走, 直到与x2深度相同,同时往上走, 直到两结点相同
    时间复杂度:

遍历树获取深度 O(n)
你们获取祖先 O(logn)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;
int n,m,res;
int father[N];
int L[N],R[N];

int find(int x,int a[]){
    int k=0;
    while(x!=1){
        a[k++] = x;
        x = father[x];
    }
    a[k++] = 1;   // 数组a来存储路径
    return k;     // k为个数
}

int main(){
    int T;
    cin >> T;
    while(T--){
        cin >> n >> m;
        for(int i=1;i<=n;i++){
            int x,y;
            cin >> x >> y;
            if(x != -1) father[x] = i;
            if(y != -1) father[y] = i;
        }
        
        while(m--){
            int l,r;
            cin >> l >> r;
            
            int x = find(l,L);
            int y = find(r,R);
    // 朴素做法                             
            for(int i=x-1,j=y-1;i>=0 && j>=0;i--,j--){
                if(L[i] == R[j]){    // 到达相同的节点
                    x--,y--;         // -2
                }
                else break;
            }
            cout << x+y << endl;
        }
        
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值