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&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++)
    {
        int u,v,w;cin>>u>>v>>w;
        add(u,v,w),add(v,u,w);
    }
    dfs(1,0);
    for(int i=1;i<=n;i++)
        ins(sum[i]);
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,query(sum[i]));
    cout<<ans<<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值