SUMMARY AGAIN

又来回顾自己的博客啦!

 

【蓝桥杯-并查集】朋友圈大小

https://blog.csdn.net/m0_38033475/article/details/79332192

1.并查集的基本使用:

① father数组和size数组

② get函数

③ add函数

2. 用map将姓名与序号对应,从而应用于并查集(并查集都是用的数字!)

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

int size[5010]; //每个并查集的size 
int father[5010];  //每个并查集的头
map<string,int> m[5010]; //每个名字对应一个序号 

int get(int a) //得到并查集的头 
{
	if(father[a]==a) //这是初始化好的,说明它没有上级,也是临界条件
		return a; 
	return get(father[a]);
}

void add(int a,int b)
{
	a= get(a);
	b= get(b); //得到二者的头
	if(a!=b)
	{
		//让b成为a的头
		father[a]=b;
		size[b]+=size[a]; //长度也要加上 
	} 
	
}


int main()
{
	for(int i=0;i<father.size();i++)
	{
		father[i]=i; //初始化为自身
		size[i]=1; //刚开始就自己一个在一个集中 
	}
	int pos=1;  
	int N;
	cin>>N;
	for(int i=0;i<N;i++)
	{
		string a,b;
		cin>>a>>b;
		if(m.count(a)==0) //该名字没有对应序号
			m[a]=pos++; //用map实现“名字-序号”的对应
		if(m.count(b)==0)
			m[b]=pos++;
		
//		add(a,b); 错 
		add(m[a],m[b]); //注意!使用序号,别写成a,b 
		
		
//		cout<<size[m[b]]<<endl; 错	   
		cout<<size[get(m[b])]<<endl; //size的更新是针对每个集的头结点的! 
	}
	return 0;
}

 

【蓝桥杯-带权并查集】接龙

https://blog.csdn.net/m0_38033475/article/details/79334746

1. 带权并查集与传统并查集的不同:增加dist数组,经过get函数之后表示该点到最终头结点的距离。

2. 比较难理解的是get函数里的改变之处。其实就是要理解:每个并查集其实都会一个接到另一个上去,所以你的father有可能也有了father,这就是并查集的递归所在。但是要明白,dist数组最初表示的是该点到这个集中的father的距离,只有经过了get函数中的更新,才能表示该点到最终头结点的距离,并且将该结点直接接到最终头结点那里去,也是打破传统并查集“一集接一集需要用到就去get函数往上找”的不变结构。

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

int size[30010];
int dist[30010];
int father[30010];

int get(int a)
{
	if(father[a]==a) //说明到了最终的头!!!最终!!! 
	{
		return a;
	} 
	//若没到最终头,说明这个集在之前被接到其它集上去了!成子集了 
	int y=father[a]; //这个子集的头(原来的头
	father[a] = get(y); //理解:同理,被接到其它集上,那么这个其它集也可能被接到另外的集上了
	//这里的get是精髓:得到的一定是最终的集的头 a带着权直接连到最终头上去 
	//而且在这个过程中,dist[y]即y到最终头的距离也成功得到了更新
	dist[a] += dist[y]; //dist[a]是a到本子集的头的距离,dist[y]是本子集的头到最终头的距离!!!! 
	return father[a]; 
} 

void add(int a,int b)
{
	a = get(a);
	b = get(b);
	if(a!=b)
	{
		father[a]=b;
		size[b]+=size[a];
		//dist[a]+=size[b]; 错 
		dist[a]=size[b]; //到本子集的头的距离的更新 
	}
}

 

 

【蓝桥杯-dfs】油田问题

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

int xx[4]={1,-1,0,0};
int yy[4]={0,0,1,-1};

void dfs(int x,int y,int n,int m,char a[1000][150])
{
    if(a[x][y]=='#')
        a[x][y]='.';
    else if(a[x][y]=='.')
        return;
    for(int i=0;i<=3;i++)
    {
        int x1=x+xx[i];
        int y1=y+yy[i];
        if(x1<0||y1<0||x1>=n||y1>=m)
            continue;
        else
            dfs(x1,y1,n,m,a);
    }
}

int main()
{
    char a[1000][150];
    int n,m;
    cin>>n>>m;
    int ans=0;
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            cin>>a[i][j];
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
        {
            if(a[i][j]=='#')
            {
                ans++;
                dfs(i,j,n,m,a);
            }
        }
    cout<<ans;
    return 0;
}

 

状态压缩DP

for(int j=0;j<n;j++)
    {
        if(k&(1<<j))
            m=max(m,a[n-1-j]);
    }
    return m;
dp[k]=inf; //最大值
            for(int j=k; j; j=(j-1)&k)
                dp[k]=min(dp[k],dp[j]+dp[j^k]);

二进制枚举,把每种情况的结果都用dp数组存储好,到时候直接调用即可,因为涉及到“父子集情况”。

求差集那里要会。

有几个标志可以告诉你用状压dp做: 

①n很小,最多取到16

②组合问题(选取问题——二进制枚举)

③求最值(联想到动态规划——而且这种组合是有包含关系的,子集之和,即“差集”之和)

 

最长上升子序列

记住lower_bound(ans,ans+n,a)-ans  得到ans数组中第一个大于等于a值的下标。大于使用upper

注意ans数组第一个数是先放进去了的,所以len初始化为1

 

最长公共子序列


#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int dp[1500][1500];
int main()
{
	char a[1500];
	char x;
	int pos=1; //一定要从1开始存
    while(scanf("%c",&x)==1 && x!=10)  //遇到空格就不输入了!空格的ASCII码是10!!!!
    {
    	a[pos]=x;
    	pos++;
	}
	pos--;
    char b[1500];
    int pos1=1;
    while(scanf("%c",&x)==1 && x!=10)
    {
    	b[pos1]=x;
    	pos1++;
	}
	pos1--;
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=pos;i++)
    {
        for(int j=1;j<=pos1;j++)
        {
            if(a[i]==b[j])
                dp[i][j]=dp[i-1][j-1]+1;  //这就是为什么要从1开始存char
            else
                dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
        }
    }
    cout<<dp[pos][pos1];
    return 0;
}
--------------------- 
作者:Joseph_LZD 
来源:CSDN 
原文:https://blog.csdn.net/m0_38033475/article/details/79492786 
版权声明:本文为博主原创文章,转载请附上博文链接!

欧几里得算法求最大公约数

int gcd(int a, int b)
{
    if(b==0)
        return a;
    return gcd(b,a%b);
}

这个很好理解,只要记住“a对b的余数和a、b的最小公约数是一样的!”

 

素数打表

给2~n范围内的所有数都打上标记,如果是素数,则该标记=1

for(int i=2;i<=n;i++)
{
	prime[i]=1;  //是素数则prime[i]=1
}

for(int i=2;i*i<=n;i++) //只在~根号n的范围内
{
	if(prime[i]==1)  //只看素数的乘积 因为合数本来就可由素数相乘而得
	{
		for(int k=i*i;k<=n;k+=i)  //注意从i*i开始迭代倍数
		{
			prime[k]=0;
		}
	}
}

费马小定理

a^(p-1) % p == 1 

一般用于判断p是否是素数,因为如果p是素数,随机取100次整数a(不要是0),都满足这个关系。

这个和素数打表的应用场景不同,素数打表一般用于要判断多个数,而费马小定理是对于判断一个巨大无比(打表没法到)的数

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
long long qmod(int a, int b, int p) { //算a^(p-1) b=p-1 
    long long res = 1;
 
    long long term = a%p;   //都要及时%p以免超限
 
    while(b) {   //二分快速幂   
        if(b&1){
            res = (res*term)%p;
        }
         
        term = (term*term)%p;
        b >>= 1;
    }
 
    return res;
}
 
bool is_prime(long long n) {   
    int i;
    for(i = 0; i < 100; ++i) {
        if(qmod(1+rand()%(n-1),n-1, n) != 1)
            break;
    }
    if(i < 100)
        return false;
    else
        return true;
}
 
int main(void) {
    int n;
    while(cin >> n) {
        if(is_prime(n))
            cout << "yes" << endl;
        else
            cout << "no" << endl;
    }
    return 0;
}

纸牌均分 、堆泥堆问题


    for(int i=0;i<N;i++){
        if(pokers[i]!=avg){
            pokers[i+1] -= avg - pokers[i]; //挨个移就好了,多了或不够就改变最近右边的那堆
            times++;
        }
    }

回顾dijkstra思路 + 链式前向星的写法 + 用普通dfs求割点(该点存在于单源路径的所有可能路径中)

struct edge
{
	int v,next;  
}e[maxn];
int p[maxm];
void init()
{
	memset(p,-1,sizeof(p));
}
int cnt=0;
void insert1(int u,int v)  //u是起点,v是终点,w是边权(这没写)
{
	e[++cnt].v=v;   //cnt是边的编号
	e[cnt].next=p[u];
	p[u]=cnt;  //p[u]是说u开头的一条边,通过这条边一直next下去可以得到u开头的直接相连的所有边
}

【动态规划】划分整数

一般dfs遇到瓶颈就去往动态规划想,想好dp代表的是什么(一般为所求)下标开一维还是二维,各代表什么意思

最重要的是去找转移方程——不同下标时的dp之间的关系!!!!

下面这道题也是求方案数,转移方程也是 += ,有异曲同工之妙!!!!【小A点菜】

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e4+5;
int dp[maxm];
int v[105];
int main()
{
	int n,m;
	cin>>n>>m;
	dp[0]=1;
	for(int i=1;i<=n;i++)
		scanf("%d",&v[i]);
	for(int i=1;i<=n;i++) //必点v[i]元
	{
		for(int j=m;j>=v[i];j--) 
		{
			dp[j]+=dp[j-v[i]]; //和划分整数区别在于,划分整数v[0]=1也算一种方案,而点菜v[0]=0
		}
	}
	cout<<dp[m];
	return 0;
}

 

 

【dp】合并石子

上一题是求方案数,而这一题是求最小值,那么,dp的转移方程中一定会出现min()

其次,转移方程都是写在循环里的嘛,而一定要根据题意灵活地想,下标是需要逆序还是顺序遍历(当前要用之前的数据)

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
 
int sum[12][12];
int dp[12][12];
int b[12];
int main()
{
	for(int i=1;i<=11;i++)
		cin>>b[i];
	for(int i=1;i<=11;i++)
	{
		for(int j=i;j<=11;j++)
		{
			int s=0;
			for(int a=i;a<=j;a++)
				s+=b[a];
			sum[i][j]=s;
		}
	}
	for(int i=11;i>=1;i--)    //这里要特别注意要逆序!!!!
	{
		for(int j=i+1;j<=11;j++)
		{
			int minn=1000;
			for(int k=i;k+1<=j;k++)
			{
				minn=min(minn,dp[i][k]+dp[k+1][j]); //找堆间的分界线
			}
			dp[i][j]=minn+sum[i][j];
		}
	}
	cout<<dp[1][11];
	return 0;
}

【dp】礼物盒

这道题,01背包的作用在于,想要看看dpwidth[100]是否==100,也就是是否能恰好装满。而真正需要dp来更新的,是所求——礼物盒的个数。

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int dpwidth[101]; //数值代表在虚宽度(下标)下的实际最大宽度之和 
int dpcnt[101]; //数值代表在虚宽度下的取实际最大宽度之和时的礼物盒数目 
int a[37];
int main()
{
	int pos=0;
	for(int i=1;i<=36;i++)
	{
		int temp_width,temp_height;
		cin>>temp_width>>temp_height;
		if(temp_height>20)
			continue;
		else
		{
				a[++pos]=temp_width;
		}
	}
	
	for(int i=1;i<=pos;i++)
	{
		for(int j=100;j>=a[i];j--)
		{
			if(dpwidth[j]<=dpwidth[j-a[i]]+a[i] && dpcnt[j]<dpcnt[j-a[i]]+1)  //注意宽度dp的比较是<=符号,因为最后最大宽度和可能有多种情况都是100 
			{
				dpwidth[j]=dpwidth[j-a[i]]+a[i];
				dpcnt[j]=dpcnt[j-a[i]]+1;
			}
		}
	}
	if(dpwidth[100]==100)
		cout<<dpcnt[100];
	return 0;
}

【dfs】引爆炸弹

差点忘了“油田”问题!!!

也就是,在dfs的过程中,改变数组元素的值!从而,在主函数中遍历数组,符合判断才进去dfs,同时计数

也就是求有“几坨”的感觉。要熟练明白。

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
 
char a[1001][1001];
int n,m;
int rowVis[1001]; //拿来剪枝的
int columnVis[1001]; //拿来剪枝的
void dfs(int x,int y)
{
	a[x][y]='2'; //表示被炸过了
    if(rowVis[x]==0) //表示行上没被清理过(清理过一次就不用再清理了)
    {
        rowVis[x]=1;
        for(int i=0;i<m;i++)
	    {
		    if(a[x][i]=='1') //行上被引爆 
			    dfs(x,i);
	    }
    }
    if(columnVis[y]==0)  //表示列上没被清理过
    {
        columnVis[y]=1;
	    for(int i=0;i<n;i++)
	    {
		    if(a[i][y]=='1') //列上被引爆 
			    dfs(i,y);
	    }
    }
}
int main()
{
	int cnt=0;
	cin>>n>>m;
	for(int i=0;i<n;i++)
			scanf("%s",a[i]);
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			if(a[i][j]=='1')  //关键!!!!
				{
					cnt++;
					dfs(i,j);
				}
		}
	}
	cout<<cnt;
	return 0;
}

互质数个数

先从2~sqrt(n)开始整除,遇到因子就把它除干净,最后剩下一个数如果>1则为最后的最大质因子。

然后套欧拉函数的公式求互质数个数:比方说12=2*2*3,质因子是2和3,那答案就是12*(1-1/2)*(1-1/3)=4

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
 
int ans[100000];
int main()
{
    long long n;
    cin>>n;
    long long N=n;
    int pos=-1;
    for(int i=2;i*i<=n;i++)
    {
        if(n%i==0) ans[++pos]=i; //找到质因子
        while(n%i==0) n=n/i;  //除干净
    }
    if(n>1) ans[++pos]=n;  //最后的最大质因子
    int res=N;
    for(int i=0;i<=pos;i++)
    {
        res=res*(1-1/ans[i]);   //套欧拉公式 求互质数个数
    }
    cout<<res;
    return 0;
}

匈牙利算法 - 用于二分图最大匹配!(求最多的组数)

郊游问题,女生必须跟喜欢的在一起,男生无所谓

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
 
int line[2001][2001];
int used[2001];
int boy[2001];
int n;
int find(int x)  //第x个女生
{
    for(int i=1;i<=n;i++)//遍历男生
    {
        if(line[x][i]==1 && used[i]==0)  //x女喜欢i男,且i男没被选
        {
            used[i]=1;  //x女生要选i男
            if(boy[i]==0 || find(boy[i]))  //如果i男单身 or i男名草有主能甩掉前女友(前女友能去递归找到新男友)
            {
                boy[i]=x;  //那么i男就跟x女走
                return 1;  //提示x女找到男朋友了
            }
        }
    }
    return 0; //x女只能落单
}
 
int main()
{
    cin>>n;
    int k,t;
    for(int i=1;i<=n;i++)
    {
        cin>>k;
        while(k--)
        {
            cin>>t;
            line[i][t]=1;
        }
    }
    int cnt=0;
    for(int x=1;x<=n;x++)  //女的来挑男的啦!
    {
        memset(used,0,sizeof(used)); //关键!表示针对每个女的,所有男的都还没被选过。记住每次都要清零。
        if(find(x)) cnt++;
    }
    cout<<cnt;
    return 0;
}

 

乳草的侵占

dfs的结构,但其实没用到递归,而是采用在dfs函数中去修改数组元素,结合“主函数中的循环操作”来实现。

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
 
int cnt=0;  //全局变量,记录牧草的剩余数
int a[105][105]; //记录地图 牧草就是-1 
int xmove[8]={0,0,-1,1,-1,-1,1,1};
int ymove[8]={1,-1,0,0,1,-1,1,-1};
int X,Y;
void eat(int x,int y,int nowWeek)
{
    for(int i=0;i<8;i++)
    {
        int xx=x+xmove[i];
        int yy=y+ymove[i];
        if(xx<1 || yy<1 || xx>Y || yy>X )
            continue;
        if(a[xx][yy]==-1)
        {
            a[xx][yy]=nowWeek+1;   
            cnt--;
        }
    }
}
 
int main()
{
    int x,y;
    cin>>X>>Y>>x>>y;
    char b[105][105];  //转存的,因为我真正想存的是数字数组。 
    for(int i=1;i<=Y;i++)  //i表示行 
        for(int j=1;j<=X;j++)  //j表示列 
        {    cin>>b[i][j];
            if(b[i][j]=='.')
            {
                	cnt++;
                	a[i][j]=-1;  //牧草 
			}
			else 
				a[i][j]=-2;   //石头 
        }
    a[Y-y+1][x]=0;
	cnt--;  //这是第0周被占领的。 
    int week=0;
    while(1)
    {
        for(int i=1;i<=Y;i++)
        {   
            for(int j=1;j<=X;j++)
            {
                if(a[i][j]==week)
                    eat(i,j,week);
            }
        }
        week++;
        if(cnt==0)
            break;
    }
    cout<<week;
    return 0;
}

 

 

【组合数】计算系数

结合二项式定理得知:(a+b)^k 中的第k+1项是 C(k,n) * a^n * b^(k-n)

所以这道题的难点其实是在于求组合数。


int calculate(int k,int n)  //计算C[k][n]
{
    if(C[k][n]!=0)
        return C[k][n];
    if(k==n || n==0)
    {
        C[k][n]=1;
        return C[k][n];
    }
    C[k][n]=calculate(k-1,n)+calculate(k-1,n-1);   //这里抓住组合数的“师傅去或不去”性质来递归求。
    C[k][n]%=10007;
    return C[k][n];
}

 

矩阵二分快速幂优化dp

对于数据量比较大,已知满足一定的多项式关系(dp),写出转移方程,用矩阵形式来表达

矩阵形如: [ai;ai-1] = A * [ai-1;ai-2] ,然后由维度关系可知A是[2,2]规模的,根据转移方程写出A

然后,写好矩阵的结构体,写好矩阵乘法和初始化单位矩阵的函数,运用矩阵二分快速幂进行求A矩阵的几次方

最后,写出初始的[ai-1;ai-2],进行相乘(注意矩阵相乘有顺序)

例题:求斐波那契数列的第n项

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn=5;
struct matrix
{
    long long m,n;
    long long a[maxn][maxn];
};
long long mod;
matrix mul(matrix A, matrix B) //矩阵乘法
{
    matrix C;
    C.m=A.m;
    C.n=B.n;
    for(int i=0;i<C.m;i++)
    {
        for(int j=0;j<C.n;j++)
        {
            C.a[i][j]=0;
            for(int k=0;k<A.n;k++)
            {
                C.a[i][j]+=A.a[i][k]*B.a[k][j]%mod;
                C.a[i][j]%mod;
            }
        }
    }
    return C;
}
 
matrix res(int m,int n)  //单位阵
{
    matrix E;
    E.m=m;
    E.n=n;
    for(int i=0;i<m;i++)
    {
        for(int j=0;j<n;j++)
        {
            E.a[i][j]=0;
            if(i==j)
                E.a[i][j]=1;
        }
    }
    return E;
}
 
int main()
{
    long long n;
    cin>>n>>mod;
    if(n==1 || n==2)
    {
    	cout<<1;
    	return 0;
	}
    matrix E=res(2,2);
    matrix A;
    A.m=2; A.n=2;
    long long a[2][2]={1,1,1,0};
    for(int i=0;i<A.m;i++)
        for(int j=0;j<A.n;j++)
            A.a[i][j]=a[i][j];
    n=n-2; //只需要A^(n-2)  因为前两项都是1,要求第三项也只要一个A相乘,以此类推
    for(; n; n>>=1)
    {
        if(n&1)
        {
            E=mul(E,A);
        }
        A=mul(A,A);
    }
    matrix start=res(2,1);
    start.m=2; start.n=1;
    long long b[2][1]={1,1};
    for(int i=0;i<start.m;i++)
        for(int j=0;j<start.n;j++)
            start.a[i][j]=b[i][j];
    matrix result=mul(E,start);
    cout<<result.a[0][0]%mod;
    return 0;
}

二分快速幂

//模板
int n;
int res;
int temp;
for(;n;n>>=1)  //!
{
	if(n&1)
		res = res*temp%mod;
	temp*=temp%mod;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值