1 并查集

1) 模板

板子


#include<bits/stdc++.h>
using namespace std;
void init()
{
	for(int i=0; i<n; i++)
		pre[i]=i;
}

int find(int a)
{
	if(pre[a]==a)
	//又写成单等了
		return a;
	else
  
		return pre[a]=find(pre[a]);//记得是pre[a]=find(pre[a]);
}

void merge(int a,int b)
{
	 a=find(a);
	 b=find(b);
	if(a==b)
		return;
	else
		pre[a]=b;
//	pre[find(a)]=find(b);相当于这个


}

2) 习题

习题集1
习题集2(洛谷)

(1)不重复序列(并查集)

第一眼都不会想到用并查集解决

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int maxn=1e5+10;
int pre[maxn];
int a[maxn];
void init()
{
	for(int i=0; i<maxn; i++)
		pre[i]=i;
}
int find(int a)
{
	if(pre[a]==a)
		return a;
	else

		return pre[a]=find(pre[a]);
}
void merge(int a,int b)
{
	a=find(a);
	b=find(b);
	if(a==b)
		return;
	else
		pre[a]=b;



}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	init();
/*
5
2 1 1 3 4
*/
	for(int i=1; i<=n; i++)
	{
		int a;
		cin>>a;
		a=find(a);//找到a的祖先
		cout<<a<<' ';
		merge(a,a+1);//a+1为a的祖先 
		//自己写的一直超时,这个更巧妙

	}

}

(2)排队询问人数(并查集应用)

错解
//(当成只维护集合大小的)(因为经过了路径压缩,每次都只会更新他的父亲的集合大小,不维护他的)
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int maxn=3e4+10;
int pre[maxn];
int a[maxn];
int num[maxn];
void init()
{
	for(int i=0; i<maxn; i++)
		
		{
			pre[i]=i;
		num[i]=1
		};
}
int find(int a)
{
	if(pre[a]==a)
		return a;
	else

		return pre[a]=find(pre[a]);
}
void merge(int a,int b)
{
	a=find(a);
	b=find(b);
	if(a==b)
		return;
	else
	{
		pre[a]=b;
		num[b]+=num[a];
		这样只能维护集合大小4:1 2 3,8:5 6
		//我们求不出来2到1 3 
	}
		



}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	init();
int t;
while(t--)
{
	char c;
	int i,j;
	cin>>c>>i>>j;
	if(c=='M')
	merge(i,j);
	if(c=='C')
	{
		if(find(i)!=find(j))
		cout<<"-1"<<endl;
		else
		{
			cout<<abs(num[i]-num[j])<<endl;
		}
	}
	
	
}

}


正解(不太理解
#include<bits/stdc++.h>
using namespace std;
int fa[30001],front[30001],num[30001],x,y,i,j,n,T,ans;    //fa[i]表示飞船i的祖先
//front[i]表示飞船i与其所在列队头的距离
                                        //num[i]表示第i列的飞船数量 
char ins;
int find(int n){                                        //查找祖先的函数 
    if(fa[n]==n)return fa[n];
    int fn=find(fa[n]);                                    //先递归找到祖先 
    front[n]+=front[fa[n]];    
	//终于懂了其实是在更新上一个m的,因为这个find在if外,每次都会执行
    /*1:23   5: 678 传入3,时假设3的父亲是2,2祖宗是1,那么要先用2到队头去更新3到队头。
    当时把1 23合并时只有2更新了
    然后现在要1 23与 4 567合并*/
    //不懂这一步
    //在回溯的时候更新front(因为更新时要用到正确的front[祖先],
                                    //所以只能在回溯的时候更新) 
    return fa[n]=fn;
}
int main(){
    cin>>T;
    for(i=1;i<=30000;++i){                                //定初值 
        fa[i]=i;
        front[i]=0;
        num[i]=1;
    }
    while(T--){
        cin>>ins>>x>>y;
        int fx=find(x);                                    //fx为x所在列的队头 
        int fy=find(y);                                    //fy同上 
        if(ins=='M'){
            front[fx]=num[fy];        //x到队头的距离就是y的长度
//即加上y所在队列的长度 
            fa[fx]=fy;                                    //将fy设为fx的祖先 
            num[fy]+=num[fx];                            //更新以fy为队头队列的长度 
            num[fx]=0;    
            //以fx为队头的队列已不存在,更新 
            // find(x); find(y);
        }
        if(ins=='C'){
            if(fx!=fy)cout<<"-1"<<endl;            //若x和y的祖先不相同,则不在同一列 
else cout<<abs(front[x]-front[y])-1<<endl;    //否则利用x和y离队头的距离算
//出它们的距离 
        }
    }
    return 0;
}


2 vj团队补题

1)动态规划(类似背包)

题目:将1变到n,要三种操作方法,+1,+x,*7,已知k种方案,和n,求x

点击查看代码
#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int maxn=1e4+10;
int n,k;
int dp[maxn];

bool check(int x)
{
 //检查这个x能否有k种情况使得1到n 
 //dp[i]=j,表示有j种方案到i
 //其实可以用dfs写出来 
	dp[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(i>x)dp[i]+=dp[i-x];//分析最后一步是怎么转移过来的
		if(i%7==0)
		dp[i]+=dp[i/7]; 
		dp[i]+=dp[i-1];
	}
	if(dp[n]==k)return true;
	else return false;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>k;
	//把1变成n有k种方法求x,枚举x 
	for(int i=2;i<n;i++)
	{
		memset(dp,0,sizeof dp);//记得每次都要重新初始化 
		if(check(i))
		{
			cout<<i<<endl;
			return 0;
		}
	}
	
	cout<<0<<endl;
	return 0;
	
}

3 cf补题 r952_1985_DIV4+r953_CF1878_DIV2

div4

1)二分

题目:
每个技能和它的cd时间,已知血条,问最少多长时间打败boss。

枚举回合数即可

点击查看代码
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
#define int unsigned long long
#define qwq ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair<int,int>
using namespace std;

int t,n,h;
struct node{
    int a,c;
}e[200005];
bool check(int x){
    int res=0;
    for(int i=1;i<=n;i++){
        res+=((x-1+e[i].c)/e[i].c)*e[i].a;//计算
        //注意:如果不开unsigned long long 这里需要加上res>=h的判断
    }
    return res>=h;
}


signed main(){
    cin>>t;
    while(t--){
        cin>>h>>n;
        for(int i=1;i<=n;i++){
            cin>>e[i].a;
        }
        for(int i=1;i<=n;i++){
            cin>>e[i].c;
        }
        int l=0,r=4e14;//其实不用开这么大
        while(l<r){//二分
            int mid=(l+r-1)/2;
            if(check(mid)){
                r=mid;
            }else{
                l=mid+1;
            }
        }
        cout<<l<<endl;

    }





    return 0;
}

2) 快速幂

题目:一个数各位之和的k倍,等于这个数的k倍的各位之和,给出l,r,求10l-10r的这种数的个数

//快速幂板子
int qmi(int m, int k, int p)
//m^k%p
{
    int res = 1 % p, t = m;
    while (k)
    {
        if (k&1) res = res * t % p;//如果为奇数
        t = t * t % p;
        k >>= 1;//指数k右移一位,相当于除2
    }
    return res;
}
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define int long long
using namespace std;

const int N = 1e6 + 100;
const int mod = 1e9 + 7;
char ch[N];
int t , n , m , z , k , a[N] , b[N];

int qmi(int m, int k)
{
    int res = 1 % mod, t = m;
    while (k)
    {
        if (k&1) res = res * t % mod;//如果为奇数
        t = t * t % mod;
        k >>= 1;//指数k右移一位,相当于除2
    }
    return res;
}

signed main() {
	cin >> t;
	while(t --) {
		cin >> n >> m >> k;
		int wd = (9 / k);
		int now = (((ksm(wd + 1 , m - n) - 1) % mod) + mod) % mod; 
		now = ksm(wd + 1 , n) % mod * now % mod;
		cout << now << "\n"; 
	}
	return 0;
}

3) 前缀和+贪心

CF1978D

image

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
typedef unsigned long long ull;
inline int read()
{
	char ch=getchar();
	int x=0,f=1;
	for(; ch<'0'||ch>'9'; ch=getchar())if(ch=='-')f=-1;
	for(; ch>='0'&&ch<='9'; ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
const int N=2e5+10;
int a[N],sum[N],max[N];
signed main()
{

	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int T=read();
	while(T--)
	{
		int n=read(),c=read();
		for(int i=1; i<=n; ++i)a[i]=read();
		a[1]+=c;
		for(int i=1; i<=n; ++i)
			sum[i]=sum[i-1]+a[i];
		max[n+1]=0;//保证后缀最大值的正常维护 
		for(int i=n; i; --i)
			max[i]=max(a[i],max[i+1]);
		int st=0;
		for(int i=1; i<=n; ++i)
		{
			if(a[i]==max[1]&&st<max[1])//这个元素第一个最大值
			{
				cout<<0<<' '; 
			}
			else
			{
				int ans=i-1;
				if(sum[i]<max[i+1])
				{
					ans++;
				}
				cout<<ans<<' ';
			}
			st=max(st,a[i]);//当前位置之前的所有元素中的最大值
		}
		cout<<'\n';
	}
}

4) 构造

重点 k为奇数或者大于最大哈曼顿值时无解,其他时候都有解,因为每次交换2|x-y|,x-y可以取到1,2,3...

题解
image

5)贪心

重点:首先要想到先执行1再执行2是最优方案。

https://www.luogu.com.cn/problem/solution/CF1978E

posted on 2024-08-07 09:38  Hoshino1  阅读(4)  评论(0编辑  收藏  举报