数据结构
1.队列
普通队列,先进先出。优先队列,内部会自动排序好。按优先级高的在队首。
队列操作:
(1)入队 .push()
(2)出队 .pop()
(3)求队中元素个数 .size()
(4)判断队列是否为空 .empty()
(5)获取队首,队尾元素 .top()
#include <queue>//队列头文件
#include <iostream>
using namespace std;
int main()
{
queue<int> q;
q.push(10);//入队操作
q.push(5);
if (!q.empty())//判断是否为空,空返回1.
{
cout<<q.size()<<endl; //返回队列中含有的元素个数
}
cout << "队头元素为:" << q.front() << endl;//输出10
cout << "队尾元素为:" << q.back() << endl;//输出5
while(!q.empty())
{
cout<<q.front<<endl;
q.pop();//弹出队首元素,清空不能一次完成,需要循环弹出队首
}
return 0;
}
2.栈
栈与队列极为相似,只不过是先进后出。操作大致与队列相同.
栈操作:
(1)入栈 .push()
(2)出栈 .pop()
(3)求栈中元素个数 .size()
(4)判断栈是否为空 .empty()
(5)获取栈顶元素 .top()
#include <stack>//栈头文件
#include <iostream>
using namespace std;
int main()
{
stack<int> s;
s.push(10);//入栈操作
s.push(5);
if (!s.empty())//判断是否为空,空返回1.
{
cout<<s.size()<<endl; //返回栈中含有的元素个数
}
cout << "栈顶元素为:" << s.top() << endl;//输出5
while(!s.empty())
{
s.pop();//弹出栈中元素,清空不能一次完成,需要循环弹出
}
return 0;
}
3.并查集
int fa[1000100],a[1000100];
int find(int x)
{
int t=x;
while(fa[t]!=t)//直接t找到根节点并保存根节点为t
t=fa[t];
int i=x,j;
while(i!=t)//路径压缩,将x上的所有的父亲节点改成t
{
j=fa[i];
fa[i]=t;
i=j;
}
return t;
// return x==fa[x]?x:fa[x]=find(fa[x]);//递归求根节点
}
void merge(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)
fa[fx]=fy;//合并两个集合
return ;
}
4.hash表
把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。最简单的hash函数就是直接将
key乘以一个质数得到的值作为地址。
5.分块
暴力的优化,把一块分为若干块。
代码长但是很好理解。
贴一个分块入门题目
洛谷P2801
#include<bits/stdc++.h>
using namespace std;
int num,block,n;//num代表分后的个数,block是每块的大小,n是总的大小
long long L[50050],R[50050],a[50050],b[50050]
,belong[50050],add[50050];
//L[]R[]记录每个分块的左右端点,a []是原始数组,b[]是a的复制数组,
//belong[]属于记录每个点属于哪个分块,add[]是优化的体现,对分后的
//整个分块直接加
void build()//预处理分块
{
block=sqrt(n);
num=n/block;
if(n%block)//剩下的为一个分块
num++;
for(int i=1;i<num;i++)
{
L[i]=(i-1)*block+1;
R[i]=i*block;
sort(b+L[i],b+R[i]+1);
}
L[num]=(num-1)*block+1;//最后一个分块需要单独记录
R[num]=n;
sort(b+L[num],b+n+1);//排序,之后可以二分求答案
for(int i=1;i<=n;i++)
belong[i]=(i-1)/block+1;//i的位置是属于这个分块的
}
void reset(int x)//更新
{
for(int i=L[x];i<=R[x];i++)
b[i]=a[i];//重新更新b数组
sort(b+L[x],b+R[x]+1);//排序
}
void change(long long left,long long right,long long val)//修改
{
if(belong[left]==belong[right])//如果更改的端点是一个分块里的
{
for(int i=left;i<=right;i++)
a[i]+=val;
reset(belong[left]);
return ;
}
else
{
for(int i=left;i<=R[belong[left]];i++)/最左边的分块
a[i]+=val;
reset(belong[left]);
for(int i=L[belong[right]];i<=right;i++)//最右边的分块
a[i]+=val;
reset(belong[right]);
for(int i=belong[left]+1;i<=belong[right]-1;i++)//中间的分块
add[i]+=val;//不用对分块里的每个元素加,只需要对这个分块整体加
}
}
long long fin(long long x,long long val)//二分答案,很容易写错
{
long long mid=0,result=0;
long long l=L[x],r=R[x];
while(l<=r)
{
mid=(l+r)/2;
if(b[mid]+add[belong[mid]]>=val)
r=mid-1,result=R[x]-mid+1;
else
l=mid+1;
}
return block-result;
}
void ask(long long left,long long right,long long val)//询问
{
long long sum=0;
if(belong[left]==belong[right])//只属于一个分块
{
for(int i=left;i<=right;i++)
if(a[i]+add[belong[i]]<val)//记得加上add
sum++;
printf("%lld\n",sum);
return ;
}
else
{
for(int i=left;i<=R[belong[left]];i++)//最左边分块
if(a[i]+add[belong[i]]<val)
{
sum++;
}
for(int i=L[belong[right]];i<=right;i++)//最右边分块
if(a[i]+add[belong[i]]<val)
{
sum++;
}
for(int i=belong[left]+1;i<=belong[right]-1;i++)//中间分块
sum+=fin(i,val);//以块遍历
printf("%lld\n",sum);
}
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
b[i]=a[i];
}
build();
long long opt,left,right,val;
while(~scanf("%lld%lld%lld%lld",&opt,&left,&right,&val))
{
if(opt==0)
{
change(left,right,val);
}
else
{
ask(left,right,val*val);
}
}
return 0;
}
题解
Minimum path.
大意是输入n,k两个数。然后输入一个行列都为n的二维字符,可以改变二维字符串的k个子母。问从左上第一个到右下最后一个(只能向右或向下走)的字典序最小的字母顺序是什么。
思路:很容易想到暴力,前面k步将字母全部改成a,然后几下k步之后自己可能所在的位置,再一个一个比较字母大小。先不论是否会超时,显然这样做有一个弊端,如果遇到不需要修改的呢?
然后我的想法就变成了那么我们就得考虑到达右下角的路径中a的个数(即为不需要修改的个数),选择包含a最多的路。然后利用dp的思想,找到含有a最多的路径。但这样写很难记录自己当前的位置,最后以失败结束。
那不然倒着想,记录到达当前位置需要修改的次数,修改次数用完的时候就是当前的所在位置,选取离终点最近的位置开始,然后比较各个可能的位置的右边下面的字母大小即可,最后输出。
下面的AC代码
#include<bits/stdc++.h>
using namespace std;
int n,k,dp[2020][2020],vis[2020][2020],maxx=2;
char a[2020][2020],ans[4040];
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%s",a[i]+1);
if(k>=2*n-1)
{
for(int i=1;i<=2*n-1;i++)
printf("a");
return 0;
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=n;j++)
if(i==0||j==0)
dp[i][j]=0x3f3f3f3f;
}
if(a[1][1]=='a')
dp[1][1]=0;
else
{
dp[1][1]=1;
if(k!=0)
{
vis[1][2]=1;
vis[2][1]=1;
maxx=3;
}
}
vis[1][1]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==1&&j==1)
continue;
if(a[i][j]!='a')
dp[i][j]+=1;
dp[i][j]+=min(dp[i-1][j],dp[i][j-1]);
if(dp[i][j]<=k)
{
maxx=max(i+j+1,maxx);
if(i+1<=n) vis[i+1][j]=1;
if(j+1<=n) vis[i][j+1]=1;
}
}
}
memset(ans,'a',sizeof(ans));
for(int i=maxx;i<=2*n;i++)
{
char minn='z';
for(int j=1;j<=n;j++)
if(i-j>=1&&i-j<=n&&vis[j][i-j])
{
minn=min(minn,a[j][i-j]);
}
for(int j=1;j<=n;j++)
{
if(i-j>=1&&i-j<=n&&vis[j][i-j]&&a[j][i-j]==minn)
{
if(i-j+1<=n) vis[j][i-j+1]=1;
if(j+1<=n) vis[j+1][i-j]=1;
}
}
ans[i]=minn;
}
for(int i=2;i<=2*n;i++)
printf("%c",ans[i]);
return 0;
}
2.New Year Permutation
并查集完成。能交换的在一个集合然后判断是否需要交换即可。
#include<bits/stdc++.h>
using namespace std;
int fa[1000100],a[1000100];
char c[400][400];
int find(int x)
{
int t=x;
while(fa[t]!=t)//找到根节点
t=fa[t];
int i=x,j;
while(i!=t)//路径压缩
{
j=fa[i];
fa[i]=t;
i=j;
}
return t;
// return x==fa[x]?x:fa[x]=find(fa[x]);//递归求根节点
}
void merge(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)
fa[fx]=fy;//合并集合
return ;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
fa[i]=i;//初始化自己为自己的祖先
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf(" %c",&c[i][j]);
if(c[i][j]=='1')
{
// cout<<"i="<<i<<" j="<<j<<endl;
merge(i,j);//合并
// for(int ii=1;ii<=n;ii++)
// cout<<"find("<<ii<<")="<<find(ii)<<" ";
// cout<<endl;
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(find(i)==find(j)&&a[i]>a[j])
{
int t=a[i];
a[i]=a[j];
a[j]=t;
}
}
}
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
return 0;
}