又来回顾自己的博客啦!
【蓝桥杯-并查集】朋友圈大小
(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;
}