数据结构 模板 快速 解题技巧(备战蓝桥杯国赛)

并查集

合根植物(2017 国赛)

}
#include <bits/stdc++.h>
using namespace std;  
const int N = 1e6;

// 查找元素x的根节点(即所在集合的代表元素)  
int find(int x)  
{  
    if (x != p[x]) // 如果x不是自己的根节点  
    {  
        p[x] = find(p[x]); // 路径压缩,将x的父节点直接指向根节点  
    }  
    return p[x]; // 返回根节点  
}  
  
int main()  
{  
    int m, n, k;  
    cin >> m >> n >> k; // 输入矩阵的行数m、列数n和要进行的合并操作次数k  
  
    // 初始化并查集,每个元素都是自己的根节点  
    for (int i = 1; i <= m * n; i++)  
    {  
        p[i] = i;  
    }  
  
    // 执行k次合并操作  
    while (k--)  
    {  
        int a, b;  
        cin >> a >> b; // 输入要合并的两个元素的编号a和b  
  
        // 合并a和b所在的集合  
        // 先找到a和b的根节点,然后将a的根节点的父节点指向b的根节点  
        p[find(a)] = find(b);  
    }  
  
    // 统计最终的集合数量(即根节点的数量)  
    int res = 0;  
    for (int i = 1; i <= m * n; i++)  
    {  
        // 如果p[i]等于i,说明i是根节点  
        res += p[i] == i;  
    }  
  
    cout << res; // 输出最终的集合数量  
  
    return 0;  
}

求组合数(动态规划)可用于取模操作

模板

#include<bits/stdc++.h>
using namespace std;
vector <int> t[100];
int main()
{
	//C(b,a)
	int a,b;
	cin>>b>>a;
	int shang=a;
	int cha=b-a;
	for(int i=0;i<=cha+1;i++)
		for(int j=0;j<=shang;j++){
			int s=0;
			if(i)s+=t[i-1][j];
			if(j)s+=t[i][j-1];
			t[i].push_back(i||j?s:1);
		}
		printf("%d",t[cha][shang]);
	return 0;
}

 (洛谷 P8848)

#include<bits/stdc++.h>using namespace std;
#define int long long 
const int N=10086;
const int mol=998244353;
int n,cp=0,cq=0;vector<int> a[N];
signed main(){
	int x;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&x),x==1?cp++:cq++;
	int m=cp-cq;
	a[0].push_back(1);
	if(m>0){
		for(int i=0;i<=cq;i++)
		for(int j=0;j<=m;j++){
			int s=0;
			if(i&&j!=m)s+=a[i-1][j+1];
			if(j)s+=a[i][j-1];
			a[i].push_back(i||j?s%mol:1);
		}
		printf("%d",a[cq][m]);
	}
	else{
		for(int i=0;i<=1-m;i++)
		for(int j=0;j<=cp;j++){
			int s=0;
			if(i)s+=a[i-1][j];
			if(j)s+=a[i][j-1];
			a[i].push_back(i||j?s%mol:1);
		}
		printf("%d",a[1-m][cp]);
	}
	return 0;
}
#include<bits/stdc++.h>
using namespace std;  
#define int long long   
const int N=10086;  
const int mol=998244353;  
  
// 其中a[i][j]表示有i个-1和j个1时,前缀和为某个特定值(取决于i和j)的序列数量  
int n,cp=0,cq=0;  
vector<int> a[N];  
  
signed main(){  
    int x;  
    scanf("%d",&n); // 读取元素数量n  
  
    // 读取每个元素的值,并统计1和-1(或0)的数量  
    for(int i=1;i<=n;i++)  
        scanf("%d",&x), x==1?cp++:cq++;  
  
    // 这里m是1的数量减去-1的数量  
    int m=cp-cq;    
    a[0].push_back(1);  
  
    // 如果m大于0(即1的数量多于-1的数量)  
    if(m>0){  
        // 通过DP计算a[i][j]的值  
        for(int i=0;i<=cq;i++) // -1的数量从0到cq  
            for(int j=0;j<=m;j++){ // 1的数量从0到m  
                int s=0;  
  
                // 如果i不为0且j不等于m,则从a[i-1][j+1]转移(即添加一个-1)  
                if(i&&j!=m)s+=a[i-1][j+1];  
  
                // 如果j不为0,则从a[i][j-1]转移(即添加一个1)  
                if(j)s+=a[i][j-1];  
  
                // 更新a[i][j]的值,注意取模  
                a[i].push_back(i||j?s%mol:1);  
            }  
  
        // 输出结果,即a[cq][m],表示有cq个-1和m个1时,前缀和为m的序列数量  
        printf("%d",a[cq][m]);  
    }  
// 如果m小于等于0(即-1的数量多于或等于1的数量)  
//直接在-1里面插孔放1即可,排列组合
    else{  
        for(int i=0;i<=1-m;i++) 
            for(int j=0;j<=cp;j++){  
                int s=0;  
                  if(i)s+=a[i-1][j];  
                  if(j)s+=a[i][j-1];  
                  a[i].push_back(i||j?s%mol:1);  
            }  
        printf("%d",a[1-m][cp]);  
    }  
    return 0;  
}

树状数组

小朋友排队(蓝桥)

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=1e6+7;
int h[N],cnt[N],arr[N];
//找左边比他大,右边比他小的一共多少个
int lowbit(int x)
{
  return x&(-x);
}

void add(int pos)
{
  for(int i=pos;i<N;i+=lowbit(i))
    arr[i]++;
}

int presum(int idx)
{
  int res=0;
  for(int i=idx;i;i-=lowbit(i))
    res+=arr[i];
  return res;
}
int main()
{ 
  int n;cin>>n;
  for(int i=1;i<=n;i++)
  {
    cin>>h[i],h[i]++;
  }
//从后往前找比他小的
  for(int i=n;i>0;i--)
  {
cnt[i]+=presum(h[i]-1);
//presum(h[i])身高小于等于和h[i]的个数
    add(h[i]);
  }
  memset(arr,0,sizeof arr);
//找比他大的,总的人数减去presum(h[i])
  for(int i=1;i<=n;i++)
  {
    cnt[i]+=presum(N)-presum(h[i]);
    add(h[i]);
  }
  ll res=0;
  for(int i=1;i<=n;i++)
  {
    res+=(ll)cnt[i]*(cnt[i]+1)/2;
  }
  cout<<res;
  return 0;
}

逆序对(当元素很大的时候处理方法,防止RE)

给的数列元素很大,直接对原序列处理会导致RE,因此可以用原序列的相对大小顺序求逆序对。

即将原先的数列排序后,用对他们的原始下标求逆序对。

#include <iostream>  
#include <algorithm>  
#include <cstring>  
using namespace std;  
typedef long long ll;  
const int N = 5e5 + 7;  
int arr[N],ranks[N];  

struct node
{
	int num,val;
}a[N];

int cmp(node a,node b)
{
	if(a.val==b.val)
		return a.num<b.num;
	else
		return a.val<b.val;
}

// 树状数组中的lowbit函数  
int lowbit(int x) {  
    return x & (-x);  
}  
  
// 在树状数组的pos位置增加1  
void add(int pos) {  
    for (int i = pos; i < N; i += lowbit(i))  
        arr[i]++;  
}  
  
// 计算树状数组中从1到idx的前缀和  
int presum(int idx) {  
    ll res = 0;  
    for (int i = idx; i; i -= lowbit(i))  
        res += arr[i];  
    return res;  
}  
  
int main() {  
    int n;  
   	scanf("%d",&n); 
    for(int i = 1; i <= n; i++) {  
       scanf("%d",&a[i].val),a[i].num=i;
    }  
    
	//对排序后数的下标求逆序对,等于对原数列求逆序对 
    sort(a+1,a+n+1,cmp);
    
    //将排序后的下标序作为要求,逆序对的数列,标上他们的下标
	for(int i=1;i<=n;i++)
		ranks[a[i].num]=i;
    ll res=0;
	
	//从前往后找比他大的,总的人数减去presum(ranks[i]) 
    for (int i = 1; i <= n; i ++) { 
        res += presum(N)-presum(ranks[i]);
        add(ranks[i]);   
    }  
    //也可以这样做 当前个数减去 presum(ranks[i]) 只不过要先添加元素 
// 	for (int i = 1; i <= n; i ++) { 
//    		add(ranks[i]); 
//        res += i-presum(ranks[i]);  
//    }
    printf("%lld",res);  
    return 0;  
}

双序列逆序对(P1966)

//以给定排序顺序,进行树状数组求逆序对
// 1 3 4 2   1 7 2 4 原序列 
// 1 4 2 3   1 3 4 2 排序后下标序列 
// ranks[1]=1,ranks[4]=3,ranks[2]=4,ranks[3]=2
#include <iostream>  
#include <algorithm>  
#include <cstring>  
using namespace std;  
typedef long long ll;  
const int N = 1e5 + 7;  
int arr[N],ranks[N];
ll mol=1e8-3;
struct node
{
	ll num,val;
}a[N],b[N];

int cmp(node a,node b)
{
	if(a.val==b.val)
		return a.num<b.num;
	else
		return a.val<b.val;
}

// 树状数组中的lowbit函数  
int lowbit(int x) {  
    return x & (-x);  
}  
  
// 在树状数组的pos位置增加1  
void add(int pos) {  
    for (int i = pos; i < N; i += lowbit(i))  
        arr[i]++;  
}  
  
// 计算树状数组中从1到idx的前缀和  
int presum(int idx) {  
    ll res = 0;  
    for (int i = idx; i; i -= lowbit(i))  
        res += arr[i];  
    return res;  
}

int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i].val,a[i].num=i;
	}
	for(int i=1;i<=n;i++)
	{
		cin>>b[i].val,b[i].num=i;
	}
	
	sort(a+1,a+1+n,cmp);
	sort(b+1,b+1+n,cmp);
	
	for(int i=1;i<=n;i++)
	{
		ranks[a[i].num]=b[i].num;
	}
	ll res=0;
	for(int i=1;i<=n;i++)
	{
		add(ranks[i]);
		res=(res+i-presum(ranks[i]))%mol;
	}
	cout<<res;
	return 0;
}

线段树

(洛谷P4588)

线段树
#include<bits/stdc++.h>
using namespace std;
long long sum[10000001];
long long mod;

//每次更新冲下往上
void update(int now)
{
//父节点为两子节点的积,根据题目
	sum[now]=(sum[now*2]*sum[now*2+1])%mod;//时刻mod
}
//往下遍历到叶子在往上求每一个父节点
void build(int now,int l,int r)
{
	if(l==r)
	{
		sum[now]=1;//建树叶子初始值为1,根据题目
		return ;
	}
	int mid=(l+r)/2;
	build(now*2,l,mid);//往左递归
	build(now*2+1,mid+1,r);//往右递归
	update(now);
}
//当前位置,总的左,总的右,需要修改的左,需要修改的右,需要修改的值
void change(int now,int l,int r,int lgo,int rgo,int nm)//套的区间修改模板
{
	if(l>=lgo&&r<=rgo)
	{
		sum[now]=nm;
		return ;
	}
	int mid=(l+r)/2;
	if(lgo<=mid)
		change(now*2,l,mid,lgo,rgo,nm);
	if(rgo>mid)
		change(now*2+1,mid+1,r,lgo,rgo,nm);
	update(now);
}
int main()
{
	int t;
	cin>>t;
	for(int i=1;i<=t;i++)
	{
		int q,op,m;
		cin>>q>>mod;
		build(1,1,q);
		for(int i=1;i<=q;i++)
		{
			cin>>op>>m;
			if(op==1)
			{
				change(1,1,q,i,i,m);
				sum[1]%=mod;
			}
			else
				change(1,1,q,m,m,1);
			
			cout<<sum[1]%mod<<endl;//输出树根
		}
	}
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值