码蹄集部分题目(2024OJ赛7.17-7.21;并查集+最小生成树+线段树+树状数组+DP)

1🐋🐋供水管线(钻石;并查集+最小生成树)

时间限制:1秒

占用内存:128M

🐟题目思路

该题目就是最小生成树的问题。我们使用选边的方法,每次选取最小边加入,用并查集检查是否已联通,如果已联通又加入该边就会产生环,是错误的,详见代码。

【码蹄集进阶塔全题解13】数据结构:并查集/线段树/树状数组 MT2137 – MT2143_哔哩哔哩_bilibili

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
struct edge
{
    int i,j,c;
    bool operator<(const edge &t) const {return c<t.c;}
}p[200];
int n,k,par[200];
​
int find(int x)
{
    if(x==par[x]) return x;
    return par[x]=find(par[x]);
}
​
bool merge(int i,int j)
{
    int x=find(i),y=find(j);
    if(x!=y) par[x]=y;
    else return false;
    return true;
}
​
int main( )
{
    cin>>n>>k;
    for(int i=1;i<=n;i++) par[i]=i;
    int x,y,c,ans=0;
    for(int i=1;i<=k;i++) cin>>p[i].i>>p[i].j>>p[i].c;
    sort(p+1,p+1+k);
    for(int i=1;i<=k;i++)
    {
        bool ok=merge(p[i].i,p[i].j);//使用并查集判断连通性
        if(ok==true) ans+=p[i].c;
    }
    cout<<ans;
    return 0;
}

2🌼🐋🐋逆序(钻石;树状数组)

时间限制:1秒

占用内存:128M

🐟题目思路

record:一个数是record就意味着他前边的数都比他小

树状数组可以求前边有多少个数比我小

【码蹄集进阶塔全题解13】数据结构:并查集/线段树/树状数组 MT2137 – MT2143_哔哩哔哩_bilibili

🥪“树状数组”知识点补充

简介

树状数组,就是一个结构为树形结构的数组,不同于二叉树的结构,它在二叉树的结构上删除了一些中间节点:

二叉树:

树状数组:

应用场景

可以解决大部分区间上面的修改以及查询的问题,例如1.单点修改,单点查询,2.区间修改,单点查询,3.区间查询,区间修改,换言之,线段树能解决的问题,树状数组大部分也可以,但是并不一定都能解决,因为线段树的扩展性比树状数组要强;树状数组的作用就是为了简化线段树。

详细讲解
前置知识—lowbit(x)运算

如何计算一个非负整数n在二进制下的最低为1及其后面的0构成的数? 例如:44 = (101100) 2,最低为1和后面的0构成的数是 (100) 2 = 4 所以 lowbit (44) = lowbit((101100)2) = (100)2 = 4,那么lowbit运算时怎么实现的呢?

44的二进制=(101100),我们对44的二进制数取反+1(也就是44的二进制补码),也即~44+1,得到-44

-44的二进制=(010100),然后我们把44和-44的二进制进行按位与运算,也即按位&得到,二进制000100,也就是十进制的4

所以lowbit(x) = x&(-x)

1问题引入

2接下来分析树状数组原理

上面是树状数组的结构图,t[x]保存以x为根的子树中叶子节点值的和,原数组为a[],那么原数组前4项的和t[4]=t[2]+t[3]+a[4]=t[1]+a[2]+t[3]+a[4]=a[1]+a[2]+a[3]+a[4]

观察节点的二进制数,可以发现,树状数组中节点x的父节点为x+lowbit(x),例如t[2]的父节点为t[4]=t[2+lowbit(2)]

3单点修改,区间查询

所以在进行单点修改的同时,更新父节点就变得非常简单,例如我们对a[1]+k,那么祖先节点t[1],t[2],t[4],t[8]都需要+k更新(因为t[]表示前缀和),此时我们就可以用lowbit操作实现.

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

单点修改实现了,如何实现区间查询呢? 例如:我们需要查询前7项的区间和sum[7]

通过图中不难看出,sum[7]=t[7]+t[6]+t[4] ,我们进一步发现,6=7-lowbit(7),4=6-lowbit(6),所以我们可以通过不断的-lowbit操作来实现求和

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

这只能求区间[1,x]的区间和,那么如何求[L,R]的区间和呢? 这时候利用前缀和相减的性质就可以了[L,R]=[1,R]−[1,L−1]

int search(int L,int R)
{
    int ans = 0;
    for(int i=L-1;i;i-=lowbit(i))
    ans-=c[i];
    for(int i=R;i;i-=lowbit(i))
    ans+=c[i];
    return 0;
}

(摘自:树状数组(详细分析+应用),看不懂打死我!-CSDN博客

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
const int N=1e5+5;
int n,c[N],chg[N];//chg[x]=y表示将x删掉后record会增加y个,那么chg数组中的最大值就是我们要的结果
​
int lowbit(int x) {return x & -x;}
void add(int x)
{
    for(;x<N;x+=lowbit(x)) c[x]++;
}
int sum(int x)
{
    int ret=0;
    for(;x>0;x-=lowbit(x)) ret+=c[x];
    return ret;
}
​
int main( )
{
    cin>>n;
    int x=0,maxm=0;
    for(int i=1;i<=n;i++)
    {
        cin>>x;
        maxm=max(maxm,x);
        int cnt=sum(x);//计算前边有多少个数比X小
        if(cnt==i-1) chg[maxm]--;//对第i个数来说,前边有i-1个数比我小,满足record;那么如果把我删掉,反而少了一个record,就要减一
        else if(cnt==i-2) chg[maxm]++;//这说明前边有1个数比我大,如果把那个数删掉,就能多一个record
        //只用考虑这两种情况,因为i-3的情况下,删除一个就也无法变成record,无能为力了,往后亦是如此
        add(x);
    }
    int ans=1;
    for(int i=1;i<=n;i++)
    {
        if(chg[i]>chg[ans]) ans=i;
    }
    cout<<ans;
    return 0;
}

3🌼🐋🐋线段树(钻石;线段树)

时间限制:1秒

占用内存:128M

🐟题目思路

线段树模板题

【码蹄集进阶塔全题解13】数据结构:并查集/线段树/树状数组 MT2137 – MT2143_哔哩哔哩_bilibili

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
const int N=1e5+5;
#define int long long
struct node
{
    int l,r,val,lz;//lz是懒标记
}tree[4*N];
int a[N];
​
void build(int p,int l,int r)
{
    tree[p].l=l,tree[p].r=r;
    if(l==r)
    {
        tree[p].val=a[l];
        return;
    }
    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    tree[p].val=tree[p*2].val+tree[p*2+1].val;
}
​
void lazy(int p,int v)
{
    int s=tree[p].l,t=tree[p].r;
    tree[p].val+=(t-s+1)*v,tree[p].lz+=v;
}
​
void pushdown(int p)
{
    lazy(2*p,tree[p].lz);
    lazy(2*p+1,tree[p].lz);
    tree[p].lz=0;
}
//带懒标记区间修改,[l,r]
//为修改区间,p为当前节点编号,c为区间节点变化值,求和非求最值
void update(int l,int r,int c,int p)
{
    int s=tree[p].l,t=tree[p].r;
    if(l<=s&&t<=r) return lazy(p,c);
    if(tree[p].lz&&s!=t) pushdown(p);
    int mid=(s+t)/2;
    if(l<=mid) update(l,r,c,p*2);
    if(r>mid) update(l,r,c,p*2+1);
    tree[p].val=tree[p*2].val+tree[p*2+1].val;
}
//带懒标记区间查询(区间求和),[l,r]为修改区间,p为当前节点编号
int query(int l,int r,int p)
{
    int s=tree[p].l,t=tree[p].r;
    if(l<=s&&t<=r) return tree[p].val;
    if(tree[p].lz) pushdown(p);
    int mid=(s+t)/2,sum=0;
    if(l<=mid) sum=query(l,r,p*2);
    if(r>mid) sum+=query(l,r,p*2+1);
    return sum;
}
​
signed main( )
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    build(1,1,n);
    while(m--)
    {
        int op,x,y,k;
        cin>>op;
        if(op==1)
        {
            cin>>x>>y>>k;
            update(x,y,k,1);
        }
        else 
        {
            cin>>x>>y;
            cout<<query(x,y,1)<<endl;
        }
    }
    return 0;
}

4🌼🐋🐋快排变形(黄金;树状数组)

时间限制:1秒

占用内存:128M

🐟题目思路

树状数组经典应用:求逆序对个数,使用归并排序

【码蹄集进阶塔全题解13】数据结构:并查集/线段树/树状数组 MT2137 – MT2143_哔哩哔哩_bilibili

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
const int N=2e5+5;
int n,a[N],b[N],c[N];
long long ans;
​
int lowbit(int x) {return x & -x;}
void add(int i,int x)
{
    for(;i<=n;i+=lowbit(i)) c[i]+=x;
}
int sum(int i)
{
    int ans=0;
    for(;i>0;i-=lowbit(i)) ans+=c[i];
    return ans;
}
bool cmp(const int x,const int y)
{
    if(b[x]==b[y]) return x>y;
    return b[x]>b[y];
}
​
int main( )
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>b[i];
        a[i]=i;
    }
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        add(a[i],1);
        ans+=sum(a[i]-1);
    }
    cout<<ans;
    return 0;
}

5🐋🐋上楼梯(黄金;线性DP)

时间限制:1秒

占用内存:128M

🐟题目思路

经典DP题,这道题目无需考虑层级之间的问题,直接使用动态转移方程:dp[n]=dp[n-1]+dp[n-2]即可,dp数组中存放到达第n层共有多少种方案。

【码蹄集进阶塔全题解14】动态规划:MT2144 – MT2150_哔哩哔哩_bilibili

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
int n,m;
long long dp[100];//注意,int会报错
int main( )
{
    cin>>m>>n;
    int total=(n-1)*m;
    dp[1]=1;
    dp[2]=2;
    for(int i=3;i<=total;i++) dp[i]=dp[i-1]+dp[i-2];
    cout<<dp[total];
    return 0;
}

6🐋🐋上楼梯2(黄金;线性DP)

时间限制:1秒

占用内存:128M

🐟题目思路

状态转移方程:dp[n]=dp[n-1]+dp[n-2]+……+dp[n-k]

【码蹄集进阶塔全题解14】动态规划:MT2144 – MT2150_哔哩哔哩_bilibili

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
const int N=1e5+5;
int n,k;
long long dp[N];
int main( )
{
    cin>>n>>k;
    dp[0]=dp[1]=1;
    for(int i=2;i<=k;i++)
    {
        for(int j=1;j<=i;j++) dp[i]=(dp[i]+dp[i-j])%114584;
    }
    for(int i=k+1;i<=n;i++)
    {
        for(int j=1;j<=k;j++) dp[i]=(dp[i]+dp[i-j])%114584;
    }
    cout<<dp[n];
    return 0;
}

⭐创作不易,点个赞吧~

⭐点赞收藏不迷路~

  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值