8/3 训练日志 (树状数组+区间覆盖+思维+01字典树)

Posted 钟钟终

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了8/3 训练日志 (树状数组+区间覆盖+思维+01字典树)相关的知识,希望对你有一定的参考价值。

P5149 会议座位

又看了一遍树状数组,通透了好多。
思路:
1.采用字典树统计记录单词原本的次序,再记录调换后的次序
2.采用树状数组求出逆序对。(本质只求正序对)
大小写敏感,26*2=52个字符,可能有52 个分支,最大5层

#include<bits/stdc++.h>
#define int long long
#define endl '\\n'

using namespace std;
const int N=5e5+5;
const int inf=0x3f3f3f3f;
int n,a[N];
int ch[N][55],idx,cnt[N],tree[N];
char s[N];
void ins(char *s,int rk)

    int p=0;
    for(int i=0;s[i];i++)
    
        int j=s[i]-'a';
        if(!ch[p][j]) ch[p][j]=++idx;
        p=ch[p][j];
    
    cnt[p]=rk;

int query(char *s)

    int p=0;
    for(int i=0;s[i];i++)
    
        int j=s[i]-'a';
        p=ch[p][j];
    
    return cnt[p];

int lowbit(int x)return x&(-x);
void add(int x,int k)

    for(;x<=n;x+=lowbit(x))
        tree[x]+=k;

int qry(int x)

    int tmp=0;
    for(;x>=1;x-=lowbit(x))
        tmp+=tree[x];
    return tmp;

signed main()

    std::cin.tie(nullptr);
    std::ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++)
    
        cin>>s;
        ins(s,i);
    
    for(int i=1;i<=n;i++)
    
        cin>>s;
        a[i]=query(s);
    
    int ans=0;
    for(int i=1;i<=n;i++)
    
        add(a[i],1);
        ans+=i-qry(a[i]);
    
    cout<<ans<<endl;
    return 0;


D. Color with Occurrences

思路:
1.思路并不复杂,代码要注意的细节较多。考察的知识点是贪心、区间覆盖问题。
2.先枚举各个字符串能匹配的区间,包含左右端点,隶属的id号。
3.在将区间排序。对于每个区间,找到相同起点能覆盖的最远的段。
具体可看代码理解,细节较多。

#include <bits/stdc++.h>
#define int long long
#define endl '\\n'
using namespace std;
const int N =1e6+100;
const int mod=998244353;
string t,s;
int n,cnt;
struct node

    int l,r,id;
e[N];
bool cmp(node e1,node e2)

    if(e1.l==e2.l)  return e1.r<e2.r;
    return e1.l<e2.l;

vector<pair<int,int>>ans;

signed main()

    int q;cin>>q;
    while(q--)
    
        ans.clear();cnt=0;
        cin>>t;cin>>n;
        int len_t=t.length();
        for(int i=1;i<=n;i++)
        
            cin>>s;int len_s=s.length();
            for(int j=0;j<len_t-len_s+1;j++)
            
                int flag=1;
                for(int k=0;k<len_s;k++)
                
                    if(s[k]!=t[k+j])
                    
                        flag=0;break;
                    
                
                if(flag)
                
                    e[++cnt].l=j+1;
                    e[cnt].r=j+len_s-1+1;
                    e[cnt].id=i;
                
            
        
        sort(e+1,e+cnt+1,cmp);
        int now=1,l=-1,r=0,g,f1=1;
        while(r<len_t)
        
            l=r+1;
            for(int i=now;i<=cnt;i++)
            
                if(e[i].l<=l&&e[i].r>=l)
                
                    if(e[i].r>r)
                    
                        r=e[i].r;g=i;
                    
                
                else if(e[i].l>l)
                
                    now=i;break;
                
            
            if(r<l)
            
                f1=0;break;
            
            ans.push_back(e[g].id,e[g].l);
        
        if(!f1)
        
            cout<<-1<<endl;continue;
        
        cout<<ans.size()<<endl;
        for(int i=0;i<ans.size();i++)
            cout<<ans[i].first<<" "<<ans[i].second<<endl;
    

    return 0;


E. Choosing The Commander

题意:每个士兵都有一个个性值,给定一个长官的pi和li,使得x^pi<li,统计满足条件得士兵个数。
思路:
1.操作1和2,分表在字典树中添加一个数和删去一个数
2.预处理出每个数字经过各个idx的数量
3.分别枚举pi和li得二进制位,ans加上pi和x异或值为0的数量,再将p指向异或为1的方向,因为下面可能还会有满足条件的士兵。

ps:编号p指向0的边表示为ch[p][0],指向1的边表示为ch[p][1]
出现编号为0,要及时退出。这很重要

#include<bits/stdc++.h>
#define int long long
#define endl '\\n'

using namespace std;
const int N=3e6+5;
const int inf=0x3f3f3f3f;
int n,a[N];
int ch[N][2],idx,sum[N];

void ins(int x)

    int p=0;   //作为起始编号,无实际意义
    for(int i=31;i>=0;i--)
    
        int j=x>>i&1;
        if(!ch[p][j]) ch[p][j]=++idx;
        p=ch[p][j];
        sum[p]++;
    

void del(int x)

    int p=0;
    for(int i=31;i>=0;i--)
    
        int j=x>>i&1;
        p=ch[p][j];
        sum[p]--;
    

int query(int x,int y)

    int p=0,res=0;  //p在j的上一层
    for(int i=31;i>=0;i--)
    
        int a=x>>i&1;
        int b=y>>i&1;
        if(b)
        
            res+=sum[ch[p][a]];
            p=ch[p][1-a];
        
        else
            p=ch[p][a];
        if(!p)  break;  //若不写会wa2
    
    return res;

signed main()

    std::cin.tie(nullptr);
    std::ios::sync_with_stdio(false);
    int q;cin>>q;
    while(q--)
    
        int g;cin>>g;
        if(g==1)
        
            int x;cin>>x;ins(x);
        
        else if(g==2)
        
            int x;cin>>x;del(x);
        
        else
        
            int x,y;cin>>x>>y;
            cout<<query(x,y)<<endl;
        
    
    return 0;

01字典树

https://www.bilibili.com/video/BV1zY4y1B7FF?spm_id_from=333.999.0.0&vd_source=91973ada1213cf6ba2cbb43a2bebd2e8

经典问题:最大异或对

题意:给定一个数组,任选两个数进行异或,得到的最大数是多少

#include<bits/stdc++.h>
#define endl '\\n'

using namespace std;
const int N=3e5+5;
const int inf=0x3f3f3f3f;
int n,a[N];
int ch[N*31][2],idx;
void ins(int x)

    int p=0;   //作为起始编号,无实际意义
    for(int i=30;i>=0;i--)
    
        int j=x>>i&1;
        if(!ch[p][j]) ch[p][j]=++idx;
        p=ch[p][j];
    

int query(int x)

    int p=0,res=0;  //p在j的上一层
    for(int i=30;i>=0;i--)
    
        int j=x>>i&1;
        if(ch[p][!j])  //走相反位
        
            res+=1<<i;p=ch[p][!j];
        
        else
            p=ch[p][j];
        if(!p)  break;  //若不写会wa2
    
    return res;

int main()

    std::cin.tie(nullptr);
    std::ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i],ins(a[i]);
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,query(a[i]));
    cout<<ans<<endl;
    return 0;


P4551 最长异或路径

性质:x ^y ^y=x
(一个数,如果它两次异或同一个数,那么它是不会有改变的)
因此,i~j的路径上的异或和,
就可以表示成 根到i的异或和 ^ 根到j的异或和
思路:
1.预处理出根到各个点的异或值。
2.对异或值建立01字典树
3.查询取出极大值。

#include<bits/stdc++.h>
#define endl '\\n'

using namespace std;
const int N=3e5+5;
const int inf=0x3f3f3f3f;
int n,a[N];
int ch[N*31][2],idx,head[N],cnt,sum[N];
struct node

    int to,nxt,w;
e[N];
void add(int from,int to,int w)

    e[++cnt].to=to;
    e[cnt].nxt=head[from];
    e[cnt].w=w;
    head[from]=cnt;

void dfs(int u,int fa)

    for(int i=head[u];i;i=e[i].nxt)
    
        int v=e[i].to;
        if(v!=fa)
        
            sum[v]=sum[u]^e[i].w;
            dfs(v,u);
        
    

void ins(int x)

    int p=0;   //作为起始编号,无实际意义
    for(int i=30;i>=0;i--)
    
        int j=x>>i&1;
        if(!ch[p][j]) ch[p][j]=++idx;
        p=ch[p][j];
    

int query(int x)

    int p=0,res=0;  //p在j的上一层
    for(int i=30;i>=0;i--)
    
        int j=x>>i&

以上是关于8/3 训练日志 (树状数组+区间覆盖+思维+01字典树)的主要内容,如果未能解决你的问题,请参考以下文章

ybtoj树状数组差分例题4区间修改区间查询

2018年全国多校算法寒假训练营练习比赛(第五场)-E情人节的电灯泡(二维树状数组单点更新+区间查询)

每日一题30.储物点的距离 (区间处理,前缀和/线段树//树状数组)

2019年8月12日训练日记

专题训练8总结

树状数组预备