1.6PTA集练7-5~7-24、7-1、7-2,堆的操作,部落冲突(二分查找)

7-5 大師と仙人との奇遇 分数 20

#include<iostream>
#include<queue>
using namespace std;
int n;
long long ans=0,num;
priority_queue<long long,vector<long long>,greater<long long>>q;//记录之前买的,用小顶堆,最上面就是最小的
int main(){cin>>n;for(int i=1;i<=n;i++){cin>>num;while(!q.empty()&&q.top()<num){ans+=(num-q.top());q.pop();}//先卖掉之前的if(i!=n)q.push(num);}while(!q.empty()){ans+=(num-q.top());q.pop();}cout<<ans;return 0;
}

 采用优先队列,每天,输入一个数,然后检测之前买的股票能不能在今天卖出去,即堆顶元素和今天的比较,不过记得比较之前要保证队列不空,今天卖完后就把今天的股票买进队列里,最后一天不买

然后最后的时候把所以股票都卖掉

7-6 插入排序 分数 10

#include<iostream>
using namespace std;
int n,arr[101];
int main(){cin>>n;for(int i=0;i<n;i++)cin>>arr[i];for(int i=1;i<n;i++){//表示要插入的元素int j=i-1,temp=arr[i];while(j>=0&&arr[j]>temp){arr[j+1]=arr[j];j--;}arr[j+1]=temp;for(int k=0;k<n;k++){cout<<arr[k];if(k!=n-1)cout<<" ";}if(i!=n-1)cout<<endl;}return 0;
}

调整牌堆时,是和插入的新牌比较,如果比它大,就把这张牌往后移,即arr[j+1]=arr[j]

while终止时,j=-1越界,或者指向第一个比他小的元素,这张牌是不应该在这里的,所以就放在后面即j+1的位置上 

7-7 冒泡排序 分数 15

#include<iostream>
using namespace std;
int n,arr[101];
void swap(int i,int j){int temp=arr[j];arr[j]=arr[i];arr[i]=temp;
}int main(){cin>>n;for(int i=0;i<n;i++)cin>>arr[i];for(int i=n-1;i>0;i--){//表示冒泡的天花板bool flag=false;//每次都建立一个标志,标志是否从发生了交换,如果没有的话就说明已经排序完成for(int j=0;j<i;j++){//表示冒泡的起点,每次都是从最底端开始冒泡if(arr[j]>arr[j+1]){swap(j,j+1);flag=1;//发生了排序交换}}if(flag){for(int k=0;k<n;k++){cout<<arr[k];if(k!=n-1)cout<<" ";}//只有发生了改变才打印if(i!=1)cout<<endl;}else{break;//没有的话就直接退出}}return 0;
}

冒泡排序的话,每次确定一个天花板,因为在冒泡的过程中,是最大的元素不断到最大的位置上,所以最大的位置就不用再逐一判断了

然后冒泡都是从最底端开始冒泡,遍历到此时的天花板上,由于这一特性,冒泡排序将会使每次循环都会交换元素,如果一旦有一次没交换元素,那就说明排序已经终止

这一点在插入排序中不适用 

7-8 小球装箱游戏 分数 200

每个球箱的数量一样多,是严格的限制

那就是先排序,然后加入

#include<iostream>
#include<algorithm>
using namespace std;
int n,br=0,bg=0,tr=0,tg=0;
struct node{int num,type;
}arr[100005];
bool cmp(node a,node b){if(a.num!=b.num){return a.num<b.num;}else{return a.type>b.type;}
}
int main(){cin>>n;//for(int i=1;i<=n;i++){cin>>arr[i].num>>arr[i].type;if(arr[i].type){tg++;}else{tr++;}}sort(arr+1,arr+n+1,cmp);for(int i=1;i<=n/2;i++){if(arr[i].type){bg++;}else{br++;}}cout<<tr-br<<" "<<tg-bg<<endl;cout<<br<<" "<<bg;return 0;
}

7-9 二路归并排序 分数 10

在归并中,先判断是不是为空(不存在),然后是不是递归底层(这里是不做处理,因为只有一个元素),接着就是做递归处理,处理完后进行操作,这个操作就是进行合并,即所谓分治法,向下递归是分,然后“治”,合并。

求叶子结点数量以及树的高度也都是分治的思路

处理到文件尾,就是在while里cin>>n

#include<iostream>
using namespace std;
int n,arr[101];
void merge(int begin,int end){if(begin>=end)return;int i=begin,j=begin,mid=(begin+end)>>1,k=mid+1;int temp[101];merge(begin,mid);merge(k,end);while(j<=mid&&k<=end){if(arr[j]<=arr[k]){temp[i++]=arr[j++];}else{temp[i++]=arr[k++];}}while(j<=mid){temp[i++]=arr[j++];}while(k<=end){temp[i++]=arr[k++];}for(int i=begin;i<=end;i++)arr[i]=temp[i];for(int i=0;i<n;i++){cout<<arr[i];if(i!=n-1)cout<<" ";}if(begin!=0||end!=n-1)cout<<endl;
}
int main(){while(cin>>n){for(int i=0;i<n;i++)cin>>arr[i];merge(0,n-1);cout<<endl;}return 0;
}

还需要注意的是,递归过程的打印,递归处理是栈的过程,即先到后出,

即最上层的是最后打印的,所谓最后打印就是最后才会结束所有本层的递归语句,即最后是打印

检测是不是最上层,就是检测左右区间,只有左右都是端点,才是最上层,不然的话就分行

7-10 成绩排名 分数 50

注意sort函数是左闭右开的,即处理左边的元素(是处理的第一个元素),不处理右边的元素(是不处理的第一个元素)

#include<iostream>
#include<algorithm>
using namespace std;
int n;
struct node{int ch,math,id;
}arr[100005];
bool cmp(node a,node b){if(a.ch!=b.ch){return a.ch>b.ch;}else if(a.math!=b.math){return a.math>b.math;}else{return a.id<b.id;}
}
int main(){cin>>n;for(int i=1;i<=n;i++){cin>>arr[i].id>>arr[i].ch>>arr[i].math;}sort(arr+1,arr+n+1,cmp);for(int i=1;i<=n;i++)cout<<arr[i].id<<endl;return 0;
}

7-11 根据后序序列和中序序列确定二叉树 分数 10

一种写法是递归函数参数是子树大小以及起点,另一种是参数是起点与终点

下面是起点与终点,有问题版

#include<iostream>
using namespace std;
int n,post[12],in[12];
struct node{int val;node*lchild,*rchild;node(int x):val(x),lchild(nullptr),rchild(nullptr){}
};
node* func(int pend,int pbegin,int iend,int ibegin){if(iend<ibegin)return nullptr;if(iend==ibegin){node* root=new node(in[iend]);return root;}int rindex;//根节点在中序的位置,同时也是左子树大小for(rindex=ibegin;rindex<=iend;rindex++){if(in[rindex]==post[pend]){break;}}node* root=new node(post[pend]);root->lchild=func(pbegin+rindex-1,pbegin,rindex-1,ibegin);root->rchild=func(pend-1,pbegin+rindex,iend,rindex+1);return root;
}
int h(node*root){if(!root)return 0;if(!root->lchild&&!root->rchild)return 1;return max(h(root->lchild),h(root->rchild))+1;
}
void pre(node*root){if(!root)return;cout<<root->val<<" ";pre(root->lchild);pre(root->rchild);
}
int main(){while(cin>>n){for(int i=1;i<=n;i++)cin>>post[i];for(int i=1;i<=n;i++)cin>>in[i];node*root=func(n,1,n,1);cout<<h(root)<<" ";pre(root);cout<<endl;}return 0;
}

下面这个是没问题的,问题出在了,直接认为根节点在中序中的位置就是此时左子树的大小,实际上并不是,只有当此时中序起点为0时,这个下标才是此时左子树的大小,不然左子树大小就是根节点下标减去中序起点 

#include<iostream>
using namespace std;
int n,post[12],in[12];
struct node{int val;node*lchild,*rchild;node(int x):val(x),lchild(nullptr),rchild(nullptr){}
};
node* func(int pend,int pbegin,int iend,int ibegin){if(iend<ibegin)return nullptr;if(iend==ibegin){node* root=new node(in[iend]);return root;}int rindex;//根节点在中序的位置for(rindex=ibegin;rindex<=iend;rindex++){if(in[rindex]==post[pend]){break;}}node* root=new node(post[pend]);int lsize=rindex-ibegin;root->lchild=func(pbegin+lsize-1,pbegin,rindex-1,ibegin);root->rchild=func(pend-1,pbegin+lsize,iend,rindex+1);return root;
}
int h(node*root){if(!root)return 0;if(!root->lchild&&!root->rchild)return 1;return max(h(root->lchild),h(root->rchild))+1;
}
void pre(node*root){if(!root)return;cout<<" "<<root->val;pre(root->lchild);pre(root->rchild);
}
int main(){while(cin>>n){for(int i=1;i<=n;i++)cin>>post[i];for(int i=1;i<=n;i++)cin>>in[i];node*root=func(n,1,n,1);cout<<h(root);pre(root);cout<<endl;}return 0;
}

在递归中,依然是先判断空,然后判断递归底层,再就是递归处理部分

减法的理解

还有就是减法的细节,给定区间端点i,j的话,j-i是j和i之间的差距,可以看成j和i之间棍子的数量

另一种看法是i和j之间的端点数量,是只包含1个端点以及中间所有端点的数量

j-i+1是包含两个区间端点的

在这里,求左子树的大小,左端点是序列起点,右端点是根节点位置,显然左端点在左子树里,右端点不在左子树里,所以减法是左子树大小=根节点位置-序列起点,即只包含区间的一个端点的元素数量,即把根节点排除在外的区间元素数量,即左子树大小

然后是要去确定左右子树的后序中序的起点与终点,就是依据子树大小来确定判断,+1-1的细节靠子树大小去试

这里需要注意,递归参数是左右都闭的,也就是说左右是严格包含的,意思就是说这个划定好的端点结点是严格处于左右子树里的(依据左子树大小去判断具体下标),

此时通过左子树大小判断时,就要采用右端点-左端点+1,因为是严格包含在子树里的,所以左右端点都被包含

上面的求法,思路是通过后序起点,然后利用左子树大小,使出来是这个数,

或者由上面减法的理解,左子树的大小也可以是左端点到根节点之间棍子的数量,而如果要定位到根节点的前一个结点,就只需要减少一个棍子,也就是p+l-1就代表右端点,p+l代表根节点位置,是第一个不在左子树里的元素的位置

即求size的大小是减法的元素数量只含一个端点的理解,求具体的下标是棍子间隔的理解 

最后注意输出格式,如果不要每行末尾的空格,空格就改成这样输出

即先输出空格再输出元素就行 

7-12 哈夫曼树 分数 25

相比于直接构造哈夫曼树以及获得哈夫曼编码还是弱了一点

#include<iostream>
#include<queue>
using namespace std;
priority_queue<int,vector<int>,greater<int>>q;
int n,num,ans=0;
int main(){cin>>n;for(int i=1;i<=n;i++){cin>>num;q.push(num);}while(q.size()>1){int num1=q.top();q.pop();int num2=q.top();q.pop();ans+=(num1+num2);q.push(num1+num2);}cout<<ans;return 0;
}

思路还是优先队列,通过优先队列来完成有规则的递归,在处理过程中就进行了每步的ans+=,完成了对ans的修改,即同一份数据会在处理过程中不断的被加,如果够小的话,而不用建完树后再对它进行处理去求路径长度

7-13 堆的操作 分数 20

#include<iostream>
using namespace std;
int n,k,arr[1005],cnt=0,id,num,m;
void swap(int i,int j){int temp=arr[j];arr[j]=arr[i];arr[i]=temp;
}
void up(int index){int par=index/2;while(par>=1){if(arr[par]>arr[index]){swap(par,index);index=par;par=index/2;}else{break;}}
}
void down(int index){int lc=2*index;while(lc<=cnt){if(lc+1<=cnt){lc=arr[lc]<arr[lc+1]?lc:lc+1;}if(arr[index]>arr[lc]){swap(index,lc);index=lc;lc=index*2;}else{break;}}
}
void insert(int x){if(cnt>=n)return;arr[++cnt]=x;up(cnt);
}
void del(){swap(1,cnt--);down(1);
}
int main(){cin>>n>>k;for(int i=1;i<=k;i++){cin>>id;if(id==1){cin>>num;insert(num);}else{del();}for(int j=1;j<=cnt;j++){cout<<arr[j];if(j!= cnt)cout<<" ";}cout<<endl;}cin>>m;for(int i=1;i<=m;i++)cin>>arr[i];cnt=m;for(int i=m/2;i>=1;i--){down(i);}for(int i=1;i<=m;i++){cout<<arr[i];if(i!=m)cout<<" ";}return 0;
}

上调操作 

 

就是从当前的位置往上找,下标从1开始的话,父母结点就是index/2;

然后操作终止的条件就是没有父母结点,即往上就没有了,即par>=1,只要还有父母结点,就还可能继续操作;然后能够继续上调的标志就是父母比自己大(因为要小顶堆),那就交换,然后更新孩子与父母,继续向上调整,直到不能,或者 没有父母,自己就已经是最小的

下调操作

下调的话可能有两个路径,选那个最小的路径换上来,

能够继续操作的前提是存在孩子结点,首先左孩子是最小的,所以迭代是要让左孩子不越界,接着判断是否存在右孩子,如果存在的话判断出一个最小的,然后尝试与此时的父结点交换,如果能换的话,说明可以继续向下,更新父节点与孩子结点,不然就说明最小的孩子都比此时的父节点大,那么已经满足堆的要求了,就不用再调整了

插入与删除 

在插入的时候有细节就是如果此时堆已经满了的话,就不继续插入,直接返回了,所以需要加一个判断是cnt>=n

如果可以继续插入,就在新位置上放入元素,然后尝试把这个元素往上调整即可

对于删除,是取出堆顶元素,然后就是要让最后的元素放在堆顶上,然后把这个元素往下调整即可

在插入删除时,都要记得对cnt进行相应的操作 

然后是第二部分,快速建堆,此时输入m后记得要更新堆的大小为m,不更新的话保留的依然是上个堆的大小 

朴素建堆就是插入一个就上移调整一个,快速建堆是先都放好,放好后再从最后一个非叶子结点开始调整,进行向下调整,往回往上走,这样的话到上面时,下面的都已经满足了堆的要求

即朴素建堆主要用上调,快速建堆主要用向下调整

7-14 堆的建立 分数 20

说的是调整已经存在的N个元素,所以采用快速建堆

#include<iostream>
using namespace std;
int n,arrmin[10005],arrmax[10005];
void swap(int&i,int&j){int temp=i;i=j;j=temp;
}
void downmin(int index){int c=index*2;while(c<=n){if(c+1<=n){c=arrmin[c]<arrmin[c+1]?c:c+1;}if(arrmin[index]>arrmin[c]){swap(arrmin[index],arrmin[c]);index=c;c=index*2;}else{break;}}
}
void downmax(int index){int c=index*2;while(c<=n){if(c+1<=n){c=arrmax[c]>arrmax[c+1]?c:c+1;}if(arrmax[index]<arrmax[c]){swap(arrmax[index],arrmax[c]);index=c;c=index*2;}else{break;}}
}
int main(){cin>>n;for(int i=1;i<=n;i++){cin>>arrmin[i];arrmax[i]=arrmin[i];}for(int i=n/2;i>=1;i--){downmin(i);downmax(i);}for(int i=1;i<=n;i++){cout<<arrmax[i];if(i!=n)cout<<" ";}cout<<endl;for(int i=1;i<=n;i++){cout<<arrmin[i];if(i!=n)cout<<" ";}return 0;
}

7-15 折半(二分)查找 分数 20

#include<iostream>
#include<algorithm>
using namespace std;
int n,arr[100005],tar,l,r;
int search(int tar,int l,int r){if(l>r)return -1;int left=l,right=r,mid=(left+right)>>1;while(left<=right){//只要区间里还有元素就可以继续操作if(arr[mid]==tar){return mid;}else if(arr[mid]<tar){left=mid+1;}else{right=mid-1;}mid=(left+right)>>1;}return -1;
}
int main(){cin>>n;for(int i=0;i<n;i++){cin>>arr[i];}sort(arr,arr+n);cin>>n;for(int i=1;i<=n;i++){cin>>tar>>l>>r;cout<<search(tar,l,r)<<endl;}return 0;
}

这个查找,先判断是不是空,即l>r,

然后在迭代中,判断能否继续操作,操作的前题是区间里还有元素,即left<=right,然后就是找

如果刚好就返回,小于目标值就往右找,大于目标值就往左找,

后面两种情况由于已经判断过了中间端点,所以在下一轮查找中直接舍弃掉,即查找的过程包含两个区间端点, 

7-16 部落冲突 分数 40

去求每个坐标位置对应的左边的面积,然后去找一个与总面积的一半最接近的位置,而且还要满足左边的大于总的减去左边的,即左边和总的一半的差值必须不是负的,必须是0或者正

要求

#include<iostream>
#include<queue>
using namespace std;
struct node{int begin,end,h;
}arr[105];
struct cmp{bool operator()(node a,node b){return a.begin>b.begin;}
}
priority_queue<node,vector<node>,cmp>q;
int n,x,y,w,h,tot=0,maxx=0,minx=1e8;
int main(){cin>>n;for(int i=1;i<=n;i++){cin>>x>>y>>w>>h;arr[i].begin=x;//也就是说绿洲的终止点是在x+w,100处arr[i].end=x+w;//端点数量是w+1,但是长度是w,就是连接的棍子数量arr[i].h=h;maxx=max(maxx,arr[i].end);tot+=w*h;q.push(arr[i]);}for(int i=arr[i].begin;i<=maxx;i++){int cnt=0;while(q.top()<i){}}return 0;
}

先按起点从小到大排,然后遍历每个绿洲,如果加上这个绿洲面积不比一般大,那就直接加,并直接下一个

上面的思路是类似于前缀和的,但是由于在输入的时候,左端点与右端点都不知道,所以就很难搞,

可解决

这里就是直接不预处理了,直接二分,在二分的过程中去计算

每次计算都遍历所有的部落,

时间复杂度应该是nlogm的一个级别

上面前缀和的话,每次输入就处理,处理的复杂度是nm差不多,再二分查找,是logm

好像还是在二分过程中去计算会好一点

这个之所以能直接二分,就是是左边累加,一直往右,就是左边面积是递增的,即使不处理也是这样,所以干脆就先不处理了,省去了处理的时间,而是只关注需要的,想要的点的信息,需要的时候就通过Get来计算得到

#include <iostream>
#include <vector>
#include <algorithm>
#include<stack>
#include<queue>
#include <unordered_set>
using namespace std;
#define ll long long
const int N = 105;
struct node {int l, r, w, h;
}s[N];
int n;
ll getd(int mid) {ll sl = 0, sr = 0;for (int i = 1; i <= n; i++) {if (s[i].r <= mid) {sl += (ll)s[i].w * s[i].h;}else if (s[i].l >= mid) {sr += (ll)s[i].w * s[i].h;}else {sl += (ll)s[i].h * (mid - s[i].l);sr += (ll)s[i].h * (s[i].r - mid);}}return sl - sr;
}
//就是说,地图上有很多块,然后划一条线,使这条线上所有左侧的块都归属于A
//剩下的都给B,然后要让A>>B
//自左到右,累积的岛屿面积是不断增加的
//也就是说每个x坐标轴都对应一个值,这个值就是自左到右的岛屿面积,类似于分布函数int main() {cin >> n;int R = 0, L = 1000;for (int i = 1; i <= n; i++) {int x, y, w, h;cin >> x >> y >> w >> h;s[i].l = x, s[i].r = x + w, s[i].w = w, s[i].h = h;R = max(R, s[i].r);L = min(L, s[i].l);}int ans = -1;ll da = 1e10;while (L <= R) {int mid = (L + R) >> 1;ll t = getd(mid);if (t < 0) {L = mid + 1;}else {da = min(da, t);R = mid - 1;ans = mid;}}//  while (ans < R && getd(ans + 1) == da) {//    ans++;//}cout << ans;return 0;
}
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
int n,x,y,w,h,l=1e8,r=0,ans;
struct node{int begin,end,h;long long s;
}arr[105];
long long tot=0;
long long gets(int index){long long sl=0;for(int i=1;i<=n;i++){if(arr[i].end<=index){sl+=arr[i].s;}else if(arr[i].begin<index){sl+=(long long)(index-arr[i].begin)*h;}}return sl*2-tot;
}
int main(){cin>>n;for(int i=1;i<=n;i++){cin>>x>>y>>w>>h;arr[i].begin=x,arr[i].end=x+w,arr[i].h=h,arr[i].s=w*h;l=min(l,x),r=max(r,x+w);tot+=arr[i].s;}while(l<=r){int mid=(l+r)>>1;long long ds=gets(mid);if(ds<0){l=mid+1;}else{r=mid-1;ans=mid;}}cout<<ans;return 0;
}
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
int n,x,y,w,h,l=1e8,r=0,ans;
struct node{int begin,end,h;long long s;
}arr[105];
long long gets(int index){long long sl=0,sr=0;for(int i=1;i<=n;i++){if(arr[i].end<=index){sl+=arr[i].s;}else if(arr[i].begin>=index){sr+=arr[i].s;}else{sl+=(long long)(index-arr[i].begin)*arr[i].h;sr+=(long long)(arr[i].end-index)*arr[i].h;}}return sl-sr;
}
int main(){cin>>n;for(int i=1;i<=n;i++){cin>>x>>y>>w>>h;arr[i].begin=x,arr[i].end=x+w,arr[i].h=h,arr[i].s=(long long)w*h;l=min(l,x),r=max(r,x+w);}while(l<=r){int mid=(l+r)>>1;long long ds=gets(mid);if(ds<0){l=mid+1;}else{r=mid-1;ans=mid;}}cout<<ans;return 0;
}

经过排查发现,问题就出现在数据范围上,就是Longlong ,

首先一定要开long long, 然后涉及Longlong的数据类型,如果是从Int型转过来的,也一定要开Longlong

还有就是longlong*2容易直接爆掉,所以还得是缓行,不能随便的乘,容易爆

这也是(left+right)>>1和left+(right-left)>>1等价,但是保险写后面的原因,因为写前面的话,如果数据比较大,就会爆掉出错

 

总之,这题的思路就是记录范围的左边与右边,然后开始二分查找,使面积差最小,小于0是不能接收的,大于0的话,就尝试继续找,只要还能继续找,就有可能继续缩小这个差距,从而找到最小的面积差

7-17 判断是否二叉排序树 分数 10

#include<iostream>
#include<string>
using namespace std;string s;int index=0;
struct node{int val;node*lchild,*rchild;node(int x):val(x),lchild(nullptr),rchild(nullptr){}
};
node* cr(){if(index>=s.size())return nullptr;if(s[index]=='*')return nullptr;node* root=new node(s[index++]-'0');root->lchild=cr();root->rchild=cr();return root;
}
bool isbst(node*root){if(!root)return true;if(!root->rchild&&!root->lchild)return true;if(root->rchild&&root->val>root->rchild->val)return false;if(root->lchild&&root->val<root->lchild->val)return false;return isbst(root->rchild)&&isbst(root->lchild);
}
int main(){node*root;while(cin>>s){index=0;node*root=cr();if(isbst(root))cout<<"YES"<<endl;else cout<<"NO"<<endl;}return 0;
}

主要是建树的细节 以及处理到文件尾的操作

采用输入string,然后对string做处理从而完成建树

这里需要注意,index表示跟踪现在这个字符串建树建到哪了,每建一个结点,index就往后移一位,表示此时的结点已经被建立了,由于给的是先序的字符串,所以是先建,再递归左右,

递归依旧是,判断是否为空,然后是否为底层,是否满足条件,然后递归处理,本层处理

在每次时,都要把index游标重新置0 

至于BST的判断逻辑,就是是否为空,是否为底层,

再就是每层的判断以及向下递归的部分

每层判断,如果有右孩子且比它大就是错的;有左孩子比他小就是错的,其它都是正确的,就递归向下,保证子树也都是BST即可 

7-18 整型关键字的散列映射 分数 25

#include<iostream>
using namespace std;
int n,p,arr[11000],num;
int main(){cin>>n>>p;for(int i=1;i<=n;i++){cin>>num;int index=num%p;if(!arr[index]){arr[index]=num;cout<<index;}else{while(arr[index]&&arr[index]!=num){index=(index+1)%p;}arr[index]=num;cout<<index;}if(i!=n)cout<<" ";}return 0;
}

就是先计算一下对应的下标,然后计算一下对应的哈希值,如果对应位置没有,就插入,不然的话就进行冲突处理,进行线性冲突处理,然后注意存储范围是模p,即0~p-1,所以就是需要注意一下处理后的坐标在这个范围内,所以index=(index+1)%p;

还有需要注意的一点是,要加入的元素可能有重复的,这也就是说哈希表里不能存重复相同的元素,而arr[index]只能够判断当前格子有没有被占用,不能判断是否和此时要插入的元素相同,所以就是要新增一个判断arr[index]!=num才行,如果不满足这个的话,也会退出循环

即这个while出来的时候,要么是第一个还没被占用的格子,要么是第一个已经存储过的相同元素

7-19 数据结构实验三 图的深度优先搜索 分数 50

#include<iostream>
using namespace std;
int n,m,u,v,g[25][25],cnt=0;
bool vis[25]={0};
void dfs(int u){cout<<u<<" ";for(int i=0;i<n;i++){if(vis[i])continue;if(g[u][i]){vis[i]=1;dfs(i);}}
}
int main(){cin>>n>>m;for(int i=1;i<=m;i++){cin>>u>>v;g[u][v]=g[v][u]=1;}for(int i=0;i<n;i++){if(!vis[i]){vis[i]=1;cnt++;dfs(i);}}cout<<endl<<cnt<<endl<<m;return 0;
}

用bool类型的vis记录每个结点的访问情况,如果访问过就不继续访问,不然的话就dfs它,下一层在它的基础上进行访问,邻接矩阵可以保证是字典序的访问顺序

在外层,用cnt去记录联通分支数

7-20 抓住那头牛 分数 25

#include<iostream>
#include<queue>
using namespace std;
int n,k,t;
bool vis[100005]={0};
queue<pair<int,int>>q;
int main(){cin>>n>>k;vis[n]=1;q.push({n,0});while(!q.empty()){int cur=q.front().first;t=q.front().second;q.pop();if(cur==k)break;if(cur*2<=100000&&!vis[cur*2]){q.push({cur*2,t+1});vis[cur*2]=1;}if(cur-1>=0&&!vis[cur-1]){q.push({cur-1,t+1});vis[cur-1]=1;}if(cur+1<=100000&&!vis[cur+1]){q.push({cur+1,t+1});vis[cur+1]=1;}}cout<<t;return 0;
}

为了防止未访问的结点在同一层(上层)中处理后会发生重复入队的情况,所以就是在入队的时候就对vis做操作,而不是在出队处理时再更改vis操作,这样可以保证在上层有多个结点可到达下层同一结点时,会只入队一个结点,从而减少了时间的复杂度

另外就是注意入队时,对下标的控制,即在合法范围内,不会出界,在遍历的时候,即只有下个位置的坐标处于0和1E5之间才会被考虑,否则不入队,直到遍历BFS搜索到目标点

7-21 任务拓扑排序 分数 10

这里就是注意要是字典序

直接层序不行,用邻接矩阵也不行,因为在上层当中,如果有大编号结点的入度比较小,先被减为0了,同时存在小编号结点,同样在上层,但是是后续被减为0的,那么在下一层当中,就会优先去访问那个大编号结点,而不是小编号结点,从而打破了字典序

即建立在层序上的,不能保证为字典序

所以应该是要用优先队列,以小顶堆,编号最小建堆,这样无论是第几层解锁到的结点,都会被优先访问从而保证字典序

#include<iostream>
#include<queue>
using namespace std;
int n,e,in[105],g[105][105],u,v,ans[105],cnt=0;
priority_queue<int,vector<int>,greater<int>>q;
bool vis[105]={0};
int main(){cin>>n>>e;for(int i=0;i<n;i++)in[i]=0;for(int i=1;i<=e;i++){cin>>u>>v;g[u][v]=1;in[v]++;}for(int i=0;i<n;i++){if(!in[i]){q.push(i);vis[i]=1;}}while(!q.empty()){int cur=q.top();ans[++cnt]=cur;q.pop();for(int i=0;i<n;i++){if(g[cur][i]&&!vis[i]){in[i]--;if(!in[i]){vis[i]=1;q.push(i);}}}}if(cnt==n){for(int i=1;i<=n;i++)cout<<ans[i]<<" ";}else{cout<<"unworkable project";}return 0;
}

7-22 信使 分数 100

堆优化

#include<iostream>
#include<queue>
using namespace std;
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>,greater<pii>>q;
int n,m,cnt=0,u,v,w,dis[105],ans=-1,head[105];
bool vis[105];
struct edge{int v,next,w;
}e[1000005];
void add(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
}
int main(){cin>>n>>m;for(int i=1;i<=n;i++)dis[i]=1e8;for(int i=1;i<=m;i++){cin>>u>>v>>w;add(u,v,w);add(v,u,w);}vis[1]=1;for(int i=head[1];i;i=e[i].next){dis[e[i].v]=e[i].w;q.push({e[i].w,e[i].v});}while(!q.empty()){int tar=q.top().second,d=q.top().first;q.pop();if(vis[tar])continue;vis[tar]=1;for(int i=head[tar];i;i=e[i].next){if(dis[e[i].v]>d+e[i].w){dis[e[i].v]=d+e[i].w;q.push({d+e[i].w,e[i].v});}}}for(int i=1;i<=n;i++){ans=max(ans,dis[i]);}if(ans>=1e8){cout<<-1;}else cout<<ans;return 0;
}

这部分是链式前向星,注意cnt是边的编号,head记录该起点的第一个边的编号,然后每个边记录下一条边的编号 

接着是堆的定义,这里定义数对,gretaer优先判断第一个数,然后相同时再判断编号

就是第一个是距离,第二个是编号,记得typedef后面要加;

还有就是边,边的结构体数组要开大点 

初始入队, 

入队不会确定结点,只有从优先队列中取出元素时才会确定被访问过,也就是说,当一个结点被访问过时,队列中可能还会存在这个元素,

注意注意,这里vis放的位置和之前那个有所不同,之前那个7-20,是严格的层序,因为是要最早遇到,所以就要避免同一层上,重复的访问到某些结点,所以在第一次遇到的时候,就要vis,因为已经确定到可以访问了,即入队时就Vis

而至于这个有优先顺序的,只有在出队时才会被确定真正访问到,这里就是出队时才vis,这也是拓扑排序要字典序时,在出队时vis,而不是入队时Vis,如果是任意的随便都行

dis始终保持为此时最小的距离

7-23 校园最短路径 分数 10

#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>,greater<pii>>q;
int n,m,w,cnt=0,head[100],pre[100],dis[100];
bool vis[100]={0};
string s,s2;
struct node{int v,w,next;
}e[10000];
void add(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
}
void path(int index){if(pre[index]==-1){cout<<s[index];return;}path(pre[index]);cout<<"->"<<s[index];
}
int main(){cin>>n>>m>>s;for(int i=0;i<n;i++)dis[i]=1e8;for(int i=1;i<=m;i++){cin>>s2>>w;add(s.find(s2[0]),s.find(s2[1]),w);add(s.find(s2[1]),s.find(s2[0]),w);}vis[0]=1,dis[0]=0,pre[0]=-1;for(int i=head[0];i;i=e[i].next){dis[e[i].v]=e[i].w;q.push({e[i].w,e[i].v});}while(!q.empty()){int tar=q.top().second;q.pop();if(vis[tar])continue;vis[tar]=1;for(int i=head[tar];i;i=e[i].next){if(dis[e[i].v]>dis[tar]+e[i].w){dis[e[i].v]=dis[tar]+e[i].w;q.push({dis[e[i].v],e[i].v});pre[e[i].v]=tar;}}}for(int i=0;i<n;i++){if(dis[i]>=1e8){cout<<"dist[A]["<<s[i]<<"]="<<256<<endl;cout<<s[i]<<endl;}else{cout<<"dist[A]["<<s[i]<<"]="<<dis[i]<<endl;path(i);cout<<endl;}}return 0;
}

7-24 最小生成树-kruskal算法 分数 15

#include<iostream>
#include<algorithm>
using namespace std;
struct edge{int u,v,w;
}e[405];
int n,m,fa[25];
int find(int x){if(fa[x]==x){return x;}fa[x]=find(fa[x]);return fa[x];
}
bool cmp(edge a, edge b){return a.w<b.w;
}
int main(){cin>>n>>m;for(int i=1;i<=n;i++)fa[i]=i;for(int i=1;i<=m;i++){cin>>e[i].u>>e[i].v>>e[i].w;}sort(e+1,e+m+1,cmp);for(int i=1;i<=m;i++){if(find(e[i].u)==find(e[i].v))continue;fa[find(e[i].u)]=find(e[i].v);cout<<min(e[i].u,e[i].v)<<","<<max(e[i].u,e[i].v)<<","<<e[i].w<<endl;}return 0;
}

用并查集,合并的时候注意是合并根节点,其它就没什么了 

 7-1 冰雹猜想。 分数 10

#include<iostream>
using namespace std;
int n,cnt=0;
int main(){cin>>n;while(n!=1){cout<<n<<" ";if(!(n%2)){n=n/2;}else{n=n*3+1;}cnt++;}cout<<1<<endl<<"count = "<<cnt+1;return 0;
}

这个题目有点问题,变化的次数输出要注意多1次 

7-2 输入正整数n,打印图形。 分数 10

#include<iostream>
using namespace std;
int n;
int main(){cin>>n;for(int i=1;i<=n-1;i++){for(int j=1;j<=n-i;j++){cout<<" ";}for(int j=1;j<=2*i-1;j++){cout<<"*";}for(int j=1;j<=n-i;j++){cout<<" ";}cout<<endl;}for(int i=1;i<=2*n-1;i++)cout<<"*";cout<<endl;for(int i=n-1;i>=1;i--){for(int j=1;j<=n-i;j++){cout<<" ";}for(int j=1;j<=2*i-1;j++){cout<<"*";}for(int j=1;j<=n-i;j++){cout<<" ";}cout<<endl;}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/233758.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用开源大语言模型开发的智能对话机器人初版原型验证

用开源大语言模型开发的智能对话机器人初版原型验证 0. 背景1. 初版检证效果展示2. 验证效果总结3. 20240108 更新 0. 背景 同事要想做一个智能对话机器人&#xff0c;特别的需求有有些几点&#xff0c; 通过预置提示词&#xff08;包括确认事项&#xff09;&#xff0c;让大…

【习题】应用程序框架

判断题 1. 一个应用只能有一个UIAbility。错误(False) 正确(True)错误(False) 2. 创建的Empty Ability模板工程&#xff0c;初始会生成一个UIAbility文件。正确(True) 正确(True)错误(False) 3. 每调用一次router.pushUrl()方法&#xff0c;页面路由栈数量均会加1。错误(Fal…

环信IM Demo登录方式如何修改为自己项目的?

在环信即时通讯云IM 官网下载Demo&#xff0c;本地运行只有手机验证码的方式登录&#xff1f;怎么更改为自己项目的Appkey和用户去进行登录呢&#xff1f; &#x1f447;&#x1f447;&#x1f447;本文以Web端为例&#xff0c;教大家如何更改代码来实现 1、 VUE2 Demo vue2…

自定义列表里面实现多选功能

需求 我们在开发过程中有时候会遇到列表里面会有多选&#xff0c;然后列表样式也要进行自定义。这里我们如果直接使用ElementUI组件el-table表格的时候这里实现起来可能比较复杂不方便&#xff0c;我们这里手写自定义一下列表里面多选的功能。 实现效果如下图所示&#xff1a…

云渲染适合什么场景下使用?

云渲染作为影视动画主流的渲染方案&#xff0c;通常云渲染服务商拥有专属的渲染农场&#xff0c;通过渲染农场庞大的高新能数量机器&#xff0c;可协助你在短时间内完成渲染任务。 云渲染使用场景有哪些&#xff1f; 1、硬件限制&#xff1a; 如果你的个人或公司电脑硬件不足…

Java内存模型(JMM)是基于多线程的吗

Java内存模型&#xff08;JMM&#xff09;是基于多线程的吗 这个问题按我的思路转换了下&#xff0c;其实就是在问&#xff1a;为什么需要Java内存模型 总结起来可以由几个角度来看待「可见性」、「有序性」和「原子性」 面试官&#xff1a;今天想跟你聊聊Java内存模型&#…

重新认识一下 vue3 应用实例

重新认识一下 vue 应用实例 &#x1f495; 创建应用实例 每个 Vue 应用都是通过 createApp 函数创建一个新的 应用实例 应用实例必须在调用了 .mount() 方法后才会渲染出来。该方法接收一个“容器”参数&#xff0c;可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串 //…

【bug】【VSCode】远程终端TERMINAL打不开

【bug】【VSCode】远程终端TERMINAL打不开 可能的原因现象分析解决 可能的原因 昨天晚上vscode在打开多个TERMINAL的情况下&#xff0c;挂了一晚上&#xff0c;今早上来看的时候全都lost connections…。然后关闭再打开就出现了如上现象。 早上一来到实验室就要debug… 现象…

【UE Niagara学习笔记】03 - 火焰喷射效果

目录 效果 步骤 一、创建粒子系统 二、制作火焰动画 三、改为GPU粒子 四、循环播放粒子动画 五、火焰喷射效果雏形 六、火焰颜色 效果 步骤 一、创建粒子系统 1. 新建一个Niagara系统&#xff0c;选择模板 命名为“NS_Flame_Thrower”&#xff08;火焰喷射&#…

IntelliJ IDEA 如何编译 Maven 工程项目

在当今的Java开发领域&#xff0c;Maven已经成为项目构建和依赖管理的标准工具。IntelliJ IDEA作为一款集成度高的Java开发环境&#xff0c;提供了许多强大的功能来简化和优化Maven项目的构建流程。本文将深入介绍如何使用IntelliJ IDEA编译Maven工程的详细步骤以及一些高级技巧…

day6:进程间的通信

思维导图&#xff1a; 实现多个进程之间的收发信息操作 create.c&#xff1a; #include <head.h> int main(int argc, const char *argv[]) {if(mkfifo("a_send_b",0664)!0){perror("");return -1;}if(mkfifo("b_send_a",0664)!0){perro…

用Java编写图书网站信息采集程序教程

目录 一、准备工作 二、分析目标网站结构 三、选择信息采集方式 四、安装Jsoup库 五、编写信息采集程序 六、注意事项 总结&#xff1a; 编写图书网站信息采集程序需要掌握HTML、CSS、JavaScript、Java等前端和后端技术。下面是一个简单的教程&#xff0c;介绍如何使用…

Java后端开发——SSM整合实验

文章目录 Java后端开发——SSM整合实验一、常用方式整合SSM框架二、纯注解方式整合SSM框架 Java后端开发——SSM整合实验 一、常用方式整合SSM框架 1.搭建数据库环境&#xff1a;MySQL数据库中创建一个名称为ssm的数据库&#xff0c;在该数据库中创建一个名称为tb_book的表 …

Spring MVC(day1)

什么是MVC MVC是一种设计模式&#xff0c;将软件按照模型、视图、控制器来划分&#xff1a; M&#xff1a;Model&#xff0c;模型层&#xff0c;指工程中的JavaBean&#xff0c;作用是处理数据 JavaBean分为两类&#xff1a; 一类称为数据承载Bean&#xff1a;专门存储业务数据…

代码随想录算法训练营第三十天|总结、332.重新安排行程、51.N皇后、37.解数独

代码随想录 (programmercarl.com) 总结 332.重新安排行程 欧拉通路和欧拉回路&#xff1a; 欧拉通路&#xff1a;对于图G来说&#xff0c;如果存在一条通路包含G的所有边&#xff0c;则该通路称为欧拉通路&#xff0c;也称欧拉路径。欧拉回路&#xff1a;如果欧拉路径是一条…

06-微服务-SpringAMQP

SpringAMQP SpringAMQP是基于RabbitMQ封装的一套模板&#xff0c;并且还利用SpringBoot对其实现了自动装配&#xff0c;使用起来非常方便。 SpringAmqp的官方地址&#xff1a;https://spring.io/projects/spring-amqp SpringAMQP提供了三个功能&#xff1a; 自动声明队列、交…

Scrum的工件

我们采用了Scrum进行开发方面的管理&#xff0c;那么所有的计划和工作都应该是透明的&#xff0c;这给了我们检查这些东西的机会&#xff0c;以便能够即时做出调整来适应即将发生的变化。 那么Scrum为我们设计了一些工件帮助我们检查我们的工作和计划&#xff0c;每个工件都有…

QT qss文件设置样式

方式一 &#xff08;单个&#xff09; 方式二 &#xff08;全局&#xff09; 所有按钮都会采用这个样式。 方式三 &#xff08;qss文件&#xff09; 创建资源文件 创建qss文件&#xff08;Button.qss&#xff09; 引用qss文件 QApplication a(argc, argv);QString qss;QFile…

元数据管理平台对比预研 Atlas VS Datahub VS Openmetadata

大家好&#xff0c;我是独孤风。元数据管理平台层出不穷&#xff0c;但目前主流的还是Atlas、Datahub、Openmetadata三家&#xff0c;那么我们该如何选择呢&#xff1f; 本文就带大家对比一下,这三个平台优势劣势。要了解元数据管理平台&#xff0c;先要从架构说起。 正文共&am…

k8s的pod基础

pod概念 pod是k8s中最小的资源管理组件。 pod也是最小化运行容器化的应用的资源管理对象。 pod是一个抽象的概念&#xff0c;可以理解为一个或者多个容器化应用的集合。 在一个pod当中运行一个容器是最常用的方式。在一个pod当中同时运行多个容器&#xff0c;在一个pod当中…