优先队列、并查集

Running Median (nowcoder.com)

题意:给你n个数,算出前i个数的中位数。

这题挺善良的,只让输出奇数个的时候的中位数,不然可太麻烦了,我们维护两个优先队列,一个代表小于中位数的值,一个代表大于中位数的值,这样中位数永远都是数量较大的那个队列的队首元素,但是添加的时候要注意,一次添加两个,不然偶数个的时候就不知道中位数是谁了,这也就是为什么我说这题还做了个人。添加完之后要记得维护一下这两个队列的大小关系,让他们的元素数量差不超过1,这样才能保证一定取到中位数。

这个做法叫对顶堆

 #include <bits/stdc++.h>using namespace std;int x[10001];priority_queue<int>le;priority_queue<int,vector<int>,greater<int>>ri;int main(){int t;cin>>t;while(t--){int n,num;cin>>num>>n;for(int i=0;i<n;i++){cin>>x[i];}//清空一下,第一发mle,不知道是不是因为我两个队列设的是局部变量while(le.size()!=0){le.pop();} while(ri.size()!=0){ri.pop();} le.push(x[0]);int k=0;cout<<num<<" "<<(n+1)/2<<endl;int now=x[0];//这里加等于是为了输出最后一轮的中位数,不然会少一个for(int i=1;i<=n;i+=2){if(le.size()>ri.size()){now=le.top();}else{now=ri.top();}cout<<now<<" ";if(i==n||i==n+1){break;}//第二发wa在这:10个一行,但是最后一行如果满十个不用输出kk++;if(k%10==0){cout<<endl;}           if(x[i]>now){ri.push(x[i]);}else{le.push(x[i]);}if(i==n-1){break;}if(x[i+1]>now){ri.push(x[i+1]);}else{le.push(x[i+1]);}while(fabs(le.size()-ri.size())>1){if(le.size()>ri.size()){int t=le.top();le.pop();ri.push(t);}else{int t=ri.top();ri.pop();le.push(t);}}}cout<<endl;}return 0;}

tokitsukaze and Soldier (nowcoder.com)

一道贪心+优先队列,我们将士兵按照s从大到小排列,因为这样我可以选择的人数才是递增的,不像我从小到大排列会造成一上来就有n个可以选择的数的情况。然后每当我要加入一个人数比当前队伍人数少的士兵时,我将价值最小的几个士兵剔除,答案一定出现在其中某一步操作的时候。

 #include <bits/stdc++.h>using namespace std;#define ll long longstruct node{ll v,s;bool operator<(const node&a)const{return v>a.v;}};node x[100001];​bool com(node a,node b){if(a.s==b.s){return a.v>b.v;}return a.s>b.s;}​int main(){int n;cin>>n;for(int i=0;i<n;i++){cin>>x[i].v>>x[i].s;}sort(x,x+n,com);//  for(int i=0;i<n;i++){//      cout<<x[i].v<<" "<<x[i].s<<endl;//  }priority_queue<node>q;ll ans=0,now=0;for(int i=0;i<n;i++){q.push(x[i]);now+=x[i].v;while(q.size()>x[i].s){now-=q.top().v;q.pop();}ans=max(ans,now);}cout<<ans<<endl;return 0;} 

感觉自己越学越菜了是怎么回事,这两场cf1300的题都a不出来,真是tfw.

[JSOI2007]建筑抢修 (nowcoder.com)

和上一道一样的题,贪心+优先队列,感觉就是堆贪心啊,之前我练过,只不过有点忘了,做了上面那题想起来一点,就不放代码了,水题。

Problem - D - Codeforces

2300的题,雨巨真的是离谱,居然教我2300的题,但是这题看下来就是一个贪心+优先队列,但是数据很烦,我被卡常数了,艹。

题意:n台电脑,每台初始电量为ai,每秒消耗bi,问最少要一个功率多少的电源能让他们坚持k秒,但是一分钟内,电源只能给一个电脑充电。

思路:一看就是二分,然后每次判断的时候我们贪心地给当前看起来最坚持不下去的充电,看看能否坚持k秒。

 #include <bits/stdc++.h>using namespace std;#define ll long longstruct node{ll a,b,s;//这个s代表比值,我就是这里被卡了,前几发全都是判断a/b,但是有了这个就不用那么多次计算除法了,绝了。bool operator<(const node m)const{return s>m.s;}};ll n,k;node x[200001];priority_queue<node>q0;inline bool fun(ll m){priority_queue<node>q(q0);ll ans=0;while(q.top().s<k-1){if(q.top().s<ans){return false;}node t=q.top();q.pop();t.a+=m;t.s=t.a/t.b;q.push(t);ans++;if(ans>k){return false;}}return true;}​int main(){std::ios::sync_with_stdio(false);scanf("%d%d",&n,&k);for(int i=0;i<n;i++){scanf("%lld",&x[i].a);}for(int i=0;i<n;i++){scanf("%lld",&x[i].b);}   for(int i=0;i<n;i++){x[i].s=x[i].a/x[i].b;q0.push(x[i]);}ll l=0,r=1e13;ll ans=1e13+1;while(l<=r){ll mid=(l+r)/2; if(fun(mid)){ans=min(ans,mid);r=mid-1;}else{l=mid+1;}}if(ans==1e13+1){printf("-1\n");return 0;} printf("%lld\n",ans);return 0;}

第 k 小 (nowcoder.com)

给一个会动态增长的数组,求第k小的数

这题看上去好像不难,但是因为数据范围太大,要求插入和查询都不能超过O(logn)因此想到了用优先队列,这个队列里只存放前k个小的数,因为k个以后的数是不可能成为答案的,所以不用纪录他们。

 #include <bits/stdc++.h>using namespace std;int x[200001];int main(){int n,m,k;cin>>n>>m>>k;for(int i=0;i<n;i++){cin>>x[i];}priority_queue<int>q;for(int i=0;i<n;i++){if(q.size()<k){q.push(x[i]);}else{if(q.top()>x[i]){q.pop();q.push(x[i]);}}}while(m--){int ca;cin>>ca;if(ca==1){int b;cin>>b;if(q.size()<k){q.push(b);}else{if(q.top()>b){q.push(b);q.pop();}}           }else{if(q.size()<k){cout<<-1<<endl;}else{cout<<q.top()<<endl;}       }}return 0;}

[JSOI2010]缓存交换 (nowcoder.com)

优先队列的题,标的五星,做下来感觉不过如此(。

贪心策略是每次选择下一次出现的位置最远的数出队,预处理一个next数组,用优先队列实时维护一个队伍,代表下一个出现的位置。

 #include <bits/stdc++.h>using namespace std;#define inf 0x3f3f3f3fint _next[100001];int x[100001];​struct node{int num;int dis;node(int n,int s){num=n;dis=s;}bool operator<(const node&a)const{return dis<a.dis;}};​void init(int n){//next[i]代表下一个x[i]的位置 map<int,int>ma;for(int i=n;i>0;i--){//离大谱,调了一节课,结果我前几次这里写成了(ma.count(x[i]!=0))居然能过80%的测试点 if(ma.count(x[i])!=0){_next[i]=ma[x[i]];ma[x[i]]=i;}else{ma[x[i]]=i;}}}​void solve(){int n,k;cin>>n>>k;for(int i=1;i<=n;i++){cin>>x[i];}init(n);int ans=0;   set<int>in;priority_queue<node>q;//维护每个时刻i队列中的每一个数的下一个的位置 for(int i=1;i<=n;i++){//如果现有队列中包含x[i] if(in.count(x[i])!=0){if(_next[i]==0){q.push(node(x[i],inf));}else{q.push(node(x[i],_next[i]));}}else{ans++; if(in.size()>=k){in.erase(q.top().num);q.pop();}in.insert(x[i]); if(_next[i]==0){q.push(node(x[i],inf));}else{q.push(node(x[i],_next[i]));}           }}   cout<<ans<<endl;}​int main(){//为了找出上面那个错误,我搁那对拍对了半天//  freopen("a.in","r",stdin);//  freopen("std.out","w",stdout);solve();return 0;}

食物链 (nowcoder.com)

很早就见过的一道题,但是一直不会写,今天雨巨上课的时候讲到了,我就跟着做了一遍。

题意:三类生物:a吃b,b吃c,c吃a,现在有n个动物每个都属于上面的某一类,然后有一些言论告诉他们你谁吃谁,谁和谁是同类,但是其中有一些假话,让你求出假话的数量。

第一种做法是雨巨讲的:种类并查集

思路:并查集嘛,看到食物链就知道了,但问题是怎么抽象化这两句话:

1-x,y同类

2-x吃y

但是仔细看一下就会发现这两句话牵扯到的x和y的状态是定量的。

x有三种状态:a,b,c

y有三种状态:a,b,c

1-x,y是同类——x,y同时为a或者同时为b或者同时为c

2-x吃y——x为a且y为b,x为b且y为c,x为c且y为a

我们只要同时维护x和y的三种状态,相当于我把每一个生物都拆成三个,分别存在并查集里,哪几个状态相连接我们就把它们放入同一个集合中。

 #include <bits/stdc++.h>using namespace std;#define ll long long//这里用了一个小技巧,表示三倍的状态://普通一点可以开三个数组,但是这题是并查集,不同数组之间不能表示连接关系//所以我们开三倍大的数组,1~n表示a,n+1~2n表示b,2n+1~3n表示cint a[200010];int fa[200010];​void init(int n){for(int i=0;i<=3*n;i++){fa[i]=i;}}​int find(int x){if(fa[x]==x){return x;}return fa[x]=find(fa[x]);}//简化版的merge,之前按ppt上的要写好几行,这个好void merge(int a,int b){fa[find(a)]=find(b);}​int main(){int n,k;cin>>n>>k;int cnt=0;init(n);for(int i=0;i<k;i++){int op,x,y;cin>>op>>x>>y;//按题目要求先特判一下假话if(x>n||y>n){cnt++;continue;}   if(op==2&&x==y){cnt++;continue;}   if(op==1){//不是同类的情况,说明这句话是假话,至于为什么只考虑了x是a的情况,因为我们是同时维护这三种状态,所以下面这两句话不是说x为a,而是x不等于y,重在看关系,下同if(find(x)==find(y+n)||find(x)==find(y+2*n)){cnt++;}else{merge(x,y);merge(x+n,y+n);merge(x+2*n,y+2*n);}}else{if(find(x)==find(y)||find(x)==find(y+2*n)){cnt++;}else{merge(x,y+n);merge(x+n,y+2*n);merge(x+2*n,y);}}//cout<<cnt<<endl;}cout<<cnt<<endl;return 0;} 

小C的周末 (nowcoder.com)

题意:有n组人分别玩不同的游戏,现在按给定次序连接两个人的电脑,只有当这组所有的人都被连在一起时他们才可以开始游戏,问每一组人最早连接到第几条线可以开始游戏。

并查集嘛,看到分组,连接几个字就大概有方向了,但是问题在于普通的并查集默认所有的人都是一组,像这样多组的情况有点像食物链那道题,如果你做过那就很容易想到用分类并查集,其实说白了就是把普通并查集里的一个集合给扩展,现在我这一个集合不仅保存了连接起来的人,还保存了每个人的游戏组别,合并的时候只要把这些属性一起合并就好了

但是这题数据范围很大,开数组会爆,所以用以前学过的知识,我们用Map去存离散的数据就好了。

 #include <bits/stdc++.h>using namespace std;//x[i][j]代表第i组中玩j号游戏的人数,map<int,int>x[100001];//记录第i组的父亲是谁int fa[100001];//cnt[i]代表第i组下有cnt[i]个人,按秩合并用的int cnt[100001];//num[i]代表总共有num[i]个人玩第i号游戏int num[100001];//答案要按照游戏的序号输出,所以我们存一下int ans[100001];​void init(int n){for(int i=1;i<=n;i++){x[i].clear();fa[i]=i;cnt[i]=1;ans[i]=0;num[i]=0;}}   ​int find(int a){if(fa[a]==a){return a;}return fa[a]=find(fa[a]);}​int main(){int n,k,m;//题目说有多组输入,我在这卡了半小时,无语了家人们while(cin>>n>>k>>m){//多组样例初始化肯定要的init(n);    for(int i=1;i<=n;i++){int t;cin>>t;num[t]++;x[i][t]++;}for(int i=0;i<m;i++){int a,b;cin>>a>>b;int A=find(a);int B=find(b);if(A==B){continue;}//这里如果不按秩合并,树会退化成链表,造成mleif(cnt[A]>cnt[B]){swap(A,B);swap(a,b);}if(A!=B){fa[A]=B;cnt[B]+=cnt[A];cnt[A]=0;//把A中的每个游戏的人数对应地加到B中for(auto it : x[A]){x[B][it.first]+=it.second;if(x[B][it.first]==num[it.first]){ans[it.first]=i+1;} }    }}for(int i=1;i<=k;i++){//如果这个游戏只有一个人玩,直接开始if(num[i]==1){cout<<0<<endl;}else if(ans[i]==0){//到死也玩不上,呜呜呜cout<<-1<<endl;}else{cout<<ans[i]<<endl;}       }}   return 0;} 

搜索与剪枝

1004-模拟战役_2021秋季算法入门班第五章习题:搜索与搜索剪枝 (nowcoder.com)

题意:两军交战,你用大炮攻打对面的大炮,大炮爆炸会产生3*3的范围伤害(会连锁反应),对面在还有大炮时会攻击你上一回合用于进攻的大炮(同样会有连锁反应)问是否能击败敌方所有大炮,如果可以,最多能剩下多少门炮。

思路:一开始看错题了,我以为只有3*3,但是还会造成连锁反应,那就很简单了,我们bfs计算出敌我双方的连通块的数量(我方还要计算出每个连通块的大炮数量),如果敌人的连通块数量多于我们至少一个那我们必败,如果我们的较多或者相等,就用大炮数量最少的连通块去打,剩下的就是答案。

代码虽然很长,但其实很水,因为我写了两个函数分别计算敌我区域(太菜了,不会一个的写法)

 #include <bits/stdc++.h>using namespace std;char mpa[5][101];char mpb[5][101];int visita[5][101];int visitb[5][101];int _next[8][2]={{-1,0},{-1,-1},{0,-1},{1,1},{1,-1},{-1,1},{1,0},{0,1}};int numa=0;int n;​int bfs(int x,int y){int num=1;queue<pair<int,int>>q;q.push(make_pair(x,y));visita[x][y]=1;while(!q.empty()){pair<int,int>t=q.front();q.pop();for(int i=0;i<8;i++){       int a=t.first+_next[i][0];int b=t.second+_next[i][1];if(a>=0&&a<4&&b>=0&&b<n&&visita[a][b]==0&&mpa[a][b]=='*'){q.push(make_pair(a,b));         visita[a][b]=1;num++;} }}return num;}​int bfs2(int x,int y){int num=1;queue<pair<int,int>>q;q.push(make_pair(x,y));visitb[x][y]=1;while(!q.empty()){pair<int,int>t=q.front();q.pop();for(int i=0;i<8;i++){       int a=t.first+_next[i][0];int b=t.second+_next[i][1];if(a>=0&&a<4&&b>=0&&b<n&&visitb[a][b]==0&&mpb[a][b]=='*'){q.push(make_pair(a,b));         visitb[a][b]=1;num++;} }}return num;}​int main(){cin>>n;for(int i=0;i<4;i++){for(int j=0;j<n;j++){cin>>mpa[i][j];}}for(int i=0;i<4;i++){for(int j=0;j<n;j++){cin>>mpb[i][j];}}for(int i=0;i<4;i++){for(int j=0;j<n;j++){if(mpa[i][j]=='*'&&visita[i][j]==0){bfs(i,j);numa++;}}}vector<int>b;for(int i=0;i<4;i++){for(int j=0;j<n;j++){if(mpb[i][j]=='*'&&visitb[i][j]==0){b.push_back(bfs2(i,j));}}}if(b.size()<numa){cout<<-1<<endl;}else{sort(b.begin(),b.end(),greater<int>());int ans=0;for(int i=0;i<=b.size()-numa;i++){ans+=b[i];}cout<<ans<<endl;}return 0;} 

[1006-NOIP2014]寻找道路_2021秋季算法入门班第五章习题:搜索与搜索剪枝 (nowcoder.com)

这题考的知识点还是挺多的,我想了一会只想到了暴搜,但是会t,所以就去看题解了,呜呜呜,太菜了。

题意:

思路:我刚拿到的时候就想到了反向建图,但是我想的是每次有新点加入队列的时候都去检测是否合法,但是这样时间复杂度很高,而题解就很巧妙,我们先反向建图从终点跑一遍bfs,标记处所有和终点联通的点,答案所求的路径一定会在这其中,而那些没有呗标记的点就是不和终点相连的点,我们去遍历这些点的邻接点,这些临界点也一定不在答案内,此时我们只需要在筛选出的这些有可能成为答案的点中跑一边bfs找出最短路就行了

但是这题我wa了好几次,调了半天,最后,tmd,居然是一个循环变量i写成了j,这还能过样例?绝了,我拿着别人的ac代码和我的对比了不下5遍,就差上对拍了。还有就是题目里说可能有环、重边什么的,我也没细想,但是好像不用深搜就没问题。

#include <bits/stdc++.h>
using namespace std;
vector<int>mp[20001];
int visit[20001];
int tes[20001];
int step[20001];
void bfs(int x){queue<int>q;q.push(x);visit[x]=1;while(!q.empty()){int t=q.front();q.pop();for(int i=0;i<mp[t].size();i++){if(!visit[mp[t][i]]){q.push(mp[t][i]);visit[mp[t][i]]=1;}}}
}
int main(){int n,m;cin>>n>>m;for(int i=1;i<=m;i++){int a,b;cin>>a>>b; mp[b].push_back(a);//反向建图}int be,en;cin>>be>>en;bfs(en);for(int i=1;i<=n;i++){if(!visit[i]){tes[i]=1;for(int j=0;j<mp[i].size();j++){tes[mp[i][j]]=1;}}}queue<int>q;q.push(en);tes[en]=1;    step[en]=0;while(!q.empty()){int t=q.front();q.pop();for(int i=0;i<mp[t].size();i++){if(!tes[mp[t][i]]&&visit[mp[t][i]]){q.push(mp[t][i]);tes[mp[t][i]]=1;step[mp[t][i]]=step[t]+1;if(mp[t][i]==be){cout<<step[t]+1<<endl;return 0;}}}} cout<<-1<<endl;return 0;
}

送外卖 (nowcoder.com)

题意:类似电梯那个题,只是每次两个选项不是互为相反数,但是,题目有个字典序最小的字符串无限长时输出“Infinity!”我就很不理解,怎么会有这种情况。我先放在这里,等我强了再来看吧,虽然我觉得这题就是有问题

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N],b[N],st[N],vis[N],flag=0,n;
char ans[N];
//确认过眼神是一个难难的题目Hhhhbool dfs(int u,int step)//能不能用step步到第u个点.
{if(u>n||u<1) return false;//假如不合法直接返回.if(u==n){ans[step]='\0';return true;}if(vis[u]){st[u]=1;return false;//不可能一直重复.会t的}vis[u]=1;//选择走第一种if(dfs(u+a[u],step+1)){ans[step]='a';if(st[u]) flag=1;return true;}//选择走第二种if(dfs(u+b[u],step+1)){ans[step]='b';if(st[u]) flag=1;return true;}return false;
}int main()
{scanf("%d",&n);for(int i=1;i<=n;i++)    scanf("%d",&a[i]);for(int i=1;i<=n;i++)    scanf("%d",&b[i]);//起始在1号小区,终点在n号小区if(dfs(1,0)){if(flag)    puts("Infinity!");else        puts(ans);}else{puts("No solution!");}return 0;
}

[NOIP2010]关押罪犯 (nowcoder.com)

以前好像就听说过这题,挺经典的。

题意:将给定的犯人们分成两组,有些犯人如果放在一组会产生冲突,冲突值已给出,每一组的冲突值为组内冲突的最大值,求这两组的冲突值的最大值的最小值。

思路:并查集+贪心,首先我们肯定是要按照冲突值从大到小排序的,因为如果不先把冲突值最大的两个人分开,后面的人冲突值肯定比前面的小,按照这个思路每次我们遇到两个没有分组的人有冲突时,都记录下来(第一要记录下这两个人不能在一组这个信息,第二要记录下这两个人分别在第几组),当一个人有组别,另一个人没有组别的时候,要将没有组别的那个人放到和另外一个人对立的那个里面。

这题虽然想了好长时间,但是还是自己写出来了,都怪雨巨给他分到了搜索这一个专题里面(,就挺奇怪的,也许是有搜索的写法,但是我没想到

#include <bits/stdc++.h>
using namespace std;
#define ll long long
struct node{ll a,b;ll val;
};
node x[200001];
ll fa[200001];
map<int,int>tes;
bool com(node a,node b){return a.val>b.val;
}int find(int a){if(fa[a]==a){return a;}return fa[a]=find(fa[a]);
}int main(){ll n,m;cin>>n>>m;for(ll i=0;i<m;i++){cin>>x[i].a>>x[i].b>>x[i].val;}//从大到小排序,首先要考虑的肯定是把最大的两个人分开 sort(x,x+m,com); fa[x[0].a]=x[0].a;fa[x[0].b]=x[0].b;//这里也要记录对立信息,调了半天这里忘了tes[x[0].a]=x[0].b;tes[x[0].b]=x[0].a;ll ans=0;for(ll i=1;i<m;i++){ //如果两个人都未分组,则他们在后续的过程中一旦一个人确定了,另一个人也必须确定 if(fa[x[i].a]==0&&fa[x[i].b]==0){fa[x[i].a]=x[i].a;fa[x[i].b]=x[i].b;  tes[x[i].a]=x[i].b;tes[x[i].b]=x[i].a;    continue;}else if(fa[x[i].a]!=0&&fa[x[i].b]==0){//如果一个人有组别了,而另一个没有,就放到对立的那个组里面 fa[x[i].b]=find(tes[find(x[i].a)]);}else if(fa[x[i].a]==0&&fa[x[i].b]!=0){fa[x[i].a]=find(tes[find(x[i].b)]);}else{int a=find(x[i].a);int b=find(x[i].b);if(a==b){ans=max(ans,x[i].val);break;}else{//如果两个人组别不同,交叉合并,b对立的与a合并,a对立的和b合并fa[find(tes[a])]=find(b);fa[find(tes[b])]=find(a);}}}cout<<ans<<endl;return 0;
}

八数码 (nowcoder.com)

题意:数字华容道,输出操作路径

思路:跟我上次在atcoder上做到的一样,搜索+map,深搜暴了所以我改成了宽搜,重点就在将每一次的状态压缩成一个字符串。

#include <bits/stdc++.h>
using namespace std;
string aim="12345678x";
map<string,int>visit;
int dir[4]={-1,1,-3,3};
char p[4]={'l','r','u','d'};
int flag=0;struct node{string now;string path;node(string a,string b){this->now=a;this->path=b;}
};void bfs(string beg,string path){queue<node>q;q.push(node(beg,path));visit[beg]=1;while(!q.empty()){node m=q.front();q.pop();if(m.now==aim){cout<<m.path<<endl;flag=1;return;}int x=0;for(int i=0;i<9;i++){if(m.now[i]=='x'){x=i;break;}}for(int i=0;i<4;i++){string t=m.now;int y=x+dir[i];if(y>=0&&y<9){if(i==0||i==1){if(x/3!=y/3){continue;}}swap(t[x],t[y]);if(visit[t]!=0){continue;}q.push(node(t,m.path+p[i]));visit[t]=1;}}}
}int main(){char t;string a="";for(int i=0;i<9;i++){cin>>t;a+=t;}bfs(,"");if(flag==0){cout<<"unsolvable"<<endl;}return 0;
}

小木棍 (nowcoder.com)

搜索加剪枝的经典题目,今天一晚上就搞了这一个题,一开始我是没有思路的,写了个暴力搜索,洛谷上水了37分,后来看雨巨的课,优化到57分,最后去看洛谷的题解,不得不说,大佬就是大佬,讲的真清楚,P1120 小木棍 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

一共七条剪枝策略,最后一条可以算两个那就是八个,关于最后一个我稍微说一下我的理解:如果我当前正在拼这根长木棍的第一个小块,并且后续已经搜索完毕返回了false,那么正常来讲我下面应该去枚举更小的下一个,但是你有没有想过一个问题:那我这根已经检测好的木块应该放到下面的哪里呢?我现在已经搜出了一个结果:当这块小木块当第一个的时候,后面任意的一种排列都不能拼成我要的小木块,那说明什么?说明我这块小木块就不应该出现在第一个这个位置,也就说明,我前面放置的有问题,所以直接退出。

#include <bits/stdc++.h>
using namespace std;
int n;
int x[100];
int visit[100];
int _next[100];//_next[i]代表后面第一个与x[i]不同的数的位置
int len;//当前正在枚举的答案
//每一个dfs函数要完成的任务就是:
//枚举需要拼接的第num个长木棒的所有可以摆在当前剩余位置的小木棍
bool dfs(int num,int rest,int index){//num代表当前正在拼第m根长木棒//rest是当前这根木棒还剩下多少没有拼//index是当前枚举到了第几根小木棒if(rest==0){//如果当前这跟木棒已经拼好了 num--;//拼下一个 rest=len;//长度为len index=0;//继续从零开始枚举 }if(rest==len&&num==0){//如果都拼好了 return true;} //我们保证先用长的小木棒去拼,贪心策略 for(int i=index;i<n;i++){ if(visit[i]!=0||x[i]>rest){continue;} //没用过并且可行 visit[i]=1;//放好这一根继续试下一根 bool tes=dfs(num,rest-x[i],i+1);visit[i]=0;if(tes){ return true;}//剪枝 if(rest==x[i]||rest==len){return false;}//剪枝:相同长度之间的替换是没有意义的 i=_next[i];} return false;
}int main(){    cin>>n;int sum=0;for(int i=0;i<n;i++){cin>>x[i];if(x[i]>50){continue;n--;i--;}sum+=x[i];}sort(x,x+n,greater<int>());_next[n-1]=n-1;for(int i=n-2;i>=0;i--){if(x[i]==x[i+1]){_next[i]=_next[i+1];}else{_next[i]=i;}} //枚举小木棒的可能长度(最小为最大的那根的长度,最大为所有木棒的长度之和) for(int i=x[0];i<=sum/2;i++){//如果枚举的这个长度不是总长度的因子(分不出完整的几段) if(sum%i!=0){continue;}len=i;bool tes=dfs(sum/i,len,0);if(tes){cout<<i<<endl;return 0;}}  cout<<sum<<endl;return 0;
} 

maze (nowcoder.com)

绝了这题写的时候脑子不在状态,两星题目调了半小时,无语了家人们,这题我写的真的是又臭又长,就这还ACMer呢?

题意:给你一个迷宫,给定起点和终点,问你最短路,但是中间会出现传送门,走传送门花费的时间为3。

思路:像这种每个位置有多种可能到达方式的题目只要在宽搜的时候把队列改为优先队列即可,这样就能保证每次走的都是最优解。

#include <bits/stdc++.h>
using namespace std;
#define f first
#define s second
char mp[301][301];
int visit[301][301];
map<pair<int,int>,pair<int,int>>mov;
int _next[4][2]={{-1,0},{1,0},{0,1},{0,-1}};
int n,m,q;
int ans;
struct node{int x,y;int step;node(int a,int b,int s){this->x=a;this->y=b;this->step=s;}bool operator<(const node a)const{return this->step>a.step;}
};void bfs(int bx,int by,int ex,int ey){priority_queue<node>q;q.push(node(bx,by,0));while(!q.empty()){node t=q.top();q.pop();if(t.x==ex&&t.y==ey){ans=t.step;}if(visit[t.x][t.y]==1){continue;}visit[t.x][t.y]=1;    if(mov.count(make_pair(t.x,t.y))!=0){if(mp[mov[make_pair(t.x,t.y)].first][mov[make_pair(t.x,t.y)].second]!='#'&&!visit[mov[make_pair(t.x,t.y)].first][mov[make_pair(t.x,t.y)].second]){q.push(node(mov[make_pair(t.x,t.y)].first,mov[make_pair(t.x,t.y)].second,t.step+3));}}  for(int i=0;i<4;i++){int c=t.x+_next[i][0];int d=t.y+_next[i][1];if(c>=0&&c<n&&d>=0&&d<m&&!visit[c][d]&&mp[c][d]!='#'){q.push(node(c,d,t.step+1));} }}
}void init(){mov.clear();ans=0;for(int i=0;i<n;i++){for(int j=0;j<m;j++){visit[i][j]=0;}}return;
}int main(){while(cin>>n>>m>>q&&n&&m){init();int bx,by,ex,ey;for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>mp[i][j];if(mp[i][j]=='S'){bx=i;by=j;}else if(mp[i][j]=='T'){ex=i;ey=j;}}}while(q--){int x1,y1,x2,y2;cin>>x1>>y1>>x2>>y2;mov[make_pair(x1,y1)]=make_pair(x2,y2);}      bfs(bx,by,ex,ey);if(ans==0){cout<<-1<<endl;continue;}cout<<ans<<endl;}return 0;
}

Ocean Currents (nowcoder.com)

和上一题类似,也是每个点都有多种到达方式,优先队列可以写但是还有更简单的,用双端队列,因为这题的路要么代价是0要么代价是1,代价为0可以直接插入队列头部,代价为1可以直接插入队列尾部,就能保证有序了,不用优先队列,这种做法有个名字叫01BFS.

#include <bits/stdc++.h>
using namespace std;
int mp[1001][1001];
int _next[8][2]={{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};
int ans;
int n,m;
int _visit[1001][1001];
struct node{int x,y;int step;node(int a,int b,int s){this->x=a;this->y=b;this->step=s;}
};void bfs(int bx,int by,int ex,int ey){deque<node>q;q.push_back(node(bx,by,0));while(!q.empty()){node t=q.front();q.pop_front();if(_visit[t.x][t.y]==1){continue;}if(t.x==ex&&t.y==ey){ans=t.step;return;}_visit[t.x][t.y]=1;for(int i=0;i<8;i++){int c=t.x+_next[i][0];int d=t.y+_next[i][1];if(c<0||c>=n||d<0||d>=m||_visit[c][d]==1){continue;}if(i==mp[t.x][t.y]){q.push_front(node(c,d,t.step));}else{q.push_back(node(c,d,t.step+1)); }}}
}void init(){ans=0;for(int i=0;i<n;i++){for(int j=0;j<m;j++){_visit[i][j]=0;}}
}int main(){cin>>n>>m;for(int i=0;i<n;i++){for(int j=0;j<m;j++){char t;cin>>t;mp[i][j]=t-'0';}} int t;cin>>t;while(t--){init();int bx,by,ex,ey;cin>>bx>>by>>ex>>ey;bx--;by--;ex--;ey--;bfs(bx,by,ex,ey);cout<<ans<<endl;}return 0;
}

Three States (nowcoder.com)

雨巨又离谱了,给我上了道2200的,但是有了前面的铺垫好像也不是很难

题意:一张图,三个国家,求最短的路将三个国家连起来

思路:不管怎么修路,这三个国家最后都会相交在路的一点,对三个国家分别进行bfs,求出他们到每个点的距离,加起来就行了,但是有一个问题,国家是有面积的,一旦连上了就相当于连上了一整块,所以这里用01bfs的技巧,将国家内的点看作代价为0的路即可

#include <bits/stdc++.h>
using namespace std;
char mp[1001][1001];
int visit[1001][1001];
int _next[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int ans[3][1001][1001];
int n,m;
struct node{int x,y;int step;node(int a,int b,int t){this->x=a;this->y=b;this->step=t;}
};void bfs(int x,int y,char tar){deque<node>q;q.push_back(node(x,y,0));while(!q.empty()){node t=q.front();q.pop_front();if(visit[t.x][t.y]){continue;}visit[t.x][t.y]=1;if(ans[tar-'0'-1][t.x][t.y]==-1){ans[tar-'0'-1][t.x][t.y]=t.step;}else{ans[tar-'0'-1][t.x][t.y]=min(ans[tar-'0'-1][t.x][t.y],t.step);}      for(int i=0;i<4;i++){int c=t.x+_next[i][0];int d=t.y+_next[i][1];if(c>=0&&c<n&&d>=0&&d<m&&!visit[c][d]){if(mp[c][d]==tar||(mp[c][d]>='1'&&mp[c][d]<='3')){q.push_front(node(c,d,t.step));}else if(mp[c][d]!='#'){q.push_back(node(c,d,t.step+1));}}}}
}void init(int t){for(int i=0;i<n;i++){for(int j=0;j<m;j++){visit[i][j]=0; ans[t][i][j]=-1;}}
}int main(){    cin>>n>>m;for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>mp[i][j];}}init(0);int flag=1;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(mp[i][j]=='1'){bfs(i,j,'1');flag=0;break;}}if(flag==0){break;}}init(1);flag=1;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(mp[i][j]=='2'){bfs(i,j,'2');flag=0;break;}}if(flag==0){break;}}init(2);flag=1;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(mp[i][j]=='3'){bfs(i,j,'3');flag=0;break;}}if(flag==0){break;}}int ou=0x3f3f3f3f;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(ans[0][i][j]>=0&&ans[1][i][j]>=0&&ans[2][i][j]>=0){//在这wa了几次,当交点是国家内的点时不用减2if(mp[i][j]>='1'&&mp[i][j]<='3'){ou=min(ou,ans[0][i][j]+ans[1][i][j]+ans[2][i][j]);}else{ou=min(ou,ans[0][i][j]+ans[1][i][j]+ans[2][i][j]-2);}             }}}if(ou==0x3f3f3f3f){cout<<-1<<endl;}else{cout<<ou<<endl;}return 0;
}

[CQOI2007]矩形RECT (nowcoder.com)

题意:给你个长方形,问有多少总方法将它分为两部分,要求每部分必须贴着长方形的边界

挺离谱的一道题,猛地一看没啥思路,看了题解大受震惊,居然直接打表,但是其实真正的做法是dfs枚举分界线,题面最后一句其实就是提示你这个分界线不会成环(否则dfs没法做了)

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;int dx[7]={0,1,0,-1,0},dy[7]={0,0,1,0,-1};
int n,m,ans,vis[10][10];void Dfs(int x,int y)
{if (x==0 || x==n || y==0 || y==m) ans++;else{vis[x][y]=true;for (int i=1; i<=4; ++i)if (!vis[x+dx[i]][y+dy[i]])Dfs(x+dx[i],y+dy[i]);vis[x][y]=false;}
}int main()
{scanf("%d%d",&n,&m);for (int i=1; i<n; ++i){memset(vis,false,sizeof(vis));vis[i][0]=true; Dfs(i,1);}for (int i=1; i<m; ++i){memset(vis,false,sizeof(vis));vis[0][i]=true; Dfs(1,i);}printf("%d\n",ans);
}

乍一看貌似根搜索没啥关系,但是我们其实可以去每次暴力搜索所有的选择,但是那样的话复杂度太高,所以我们需要剪枝

第一个问题就是我们真的要每个状态都去取模吗?这样显然是不行的,一方面有可能爆long long,另一方面代价太高了,所以我们想一想,当我在一个数后面添加一位的时候就相当于把它乘10+这个数,那新数的余数就是原数的余数*10+这个数再取余(模运算的性质)这样计算的代价就大大减少了,但是搜索的复杂度还是没有降低,所以我们再想一想:当我搜到了两个余数相同的状态时,数字较长的那个状态永远都不可能产生更优的答案。因此剪枝的策略就出来了,我们bfs每次只保存第一个出现的状态即可

牛客算法竞赛入门笔记2相关推荐

  1. 牛客算法竞赛入门笔记1

    2021-10-20:昨天开的新坑,看了前几集感觉还可以,后悔为什么没早点跟着学,以前就感觉到了自己的知识体系太散了,这个课好像是11月还是12月结束,她说能达到icpc铜牌水平,我姑且相信好吧,希望 ...

  2. 左程云牛客算法初级班笔记

    第一课 第二课 第三课 第四课 第五课 第六课 第七课 第八课 个人的上课笔记 记录了一些算法题细节的理解要点 第一课 估计递归大小复杂度的通式 a子过程样本量 b子过程发生了多少次 0: 除去子过程 ...

  3. 算法竞赛入门笔记—推荐oj

    西班牙Valladolid大学的UVaOJ,网址http://uva.onlinejudge.org/.建议Firefox浏览器.特殊分卷--AOAPC||. 其他著名OJ ZOJ(浙江大学) POJ ...

  4. 【算法竞赛入门经典】读书笔记

    前言 寒假期间准备一下练一下OJ,对于我这个小菜鸟来说,打稳基础很是关键.听说过[算法竞赛入门经典]的大名,加上自己学习的是 C++ ,而这本书用的是 C ,多学一门语言也是不错的.因此决定每天最少看 ...

  5. [读书笔记]《算法竞赛入门经典》第1章

    书名:算法竞赛-入门经典 第2版 作者:刘汝佳 类别:读书笔记 文章目录 前言 第1章 程序设计入门 1.1 算术表达式 1.2 变量及其输入 1.3 顺序结构程序设计(Sequential Prog ...

  6. 算法竞赛入门(2)学习笔记——循环结构程序设计

    C语言学习 一:for循环 二:while循环和do-while循环 三:循环的代价 四:算法竞赛中的输入输出框架 五:习题 5.1 水仙花数 5.2 韩信点兵 5.3 倒三角形 5.4 子序列的和 ...

  7. 多阶段决策问题——DAG(算法竞赛入门经典笔记)

    多阶段决策问题--DAG 本文为算法竞赛入门经典第九章第三节的笔记(刘汝佳. 算法竞赛入门经典.第2版[M]. 清华大学出版社, 2014.) 多阶段决策问题:每作一次决策就可以得到解的一部分,当所有 ...

  8. 蓝桥杯备考——算法竞赛入门经典(第2版)学习笔记2

    算法竞赛入门经典(第2版)学习笔记2 第二章 循环结构程序设计 2.1 for循环 2.2 while 循环和do-while 循环 2.3 循环的代价 2.4 算法竞赛中的输入输出框架 2.5 注解 ...

  9. 算法竞赛入门经典(刘汝佳)——代码笔记

    Reference: <算法竞赛入门经典>(刘汝佳)第一版.第二版 ------------------------------------------------------------ ...

最新文章

  1. android 固定中间焦点,在Android上将相机焦点设置为受控固定距离
  2. 关于域帐户将计算机加入域登陆上限问题
  3. 一个虐你千百遍的问题:“RPC好,还是RESTful好?”
  4. 10个让人厌烦的编程语言
  5. 推荐系统的构建:从经典到深度学习方法
  6. 20个jQuery 图片及多媒体画廊插件
  7. “约见”面试官系列之常见面试题第三十七篇之CSS3新属性(建议收藏)
  8. linux-wc命令
  9. jQuery Portamento 滑动定位
  10. 随想录(qemu仿真linux kernel)
  11. C语言随笔小算法:单向链表
  12. linux set 39 date 39,Linux date命令
  13. python scipy版本_Py之Scipy:Python库之Scipy库的简介、安装、使用方法详细攻略
  14. phpspider 简单使用
  15. 【前端】【HTML+CSS+JavaScript(JS)】简易登陆界面的实现
  16. android 6.0 vs ios9,安卓6.0彻底看呆!iOS 9安装率曝光 完胜
  17. 分页查询优化方案总结
  18. 设备巡检维修报备小程序开发制作功能介绍
  19. 免驱无线网卡插到电脑上突然驱动变成瑞昱网卡了无法正常联网
  20. SQL注入漏洞-SQL注入原理与实践

热门文章

  1. error C2365: : redefinition:previous definition was
  2. 3朵红花=60,1朵红花+2朵蓝花=30,1朵蓝花-两朵黄花=3,1朵黄花+1朵红花+1朵蓝花=?...
  3. module java.base does not opens java.lang to unnamed module @‘‘xxxxxxxx‘‘
  4. R语言里的点样式pch
  5. 青春是黄鹤·《致我们终将逝去的青春》
  6. 所有DIN 紧固件 外形规格对照表
  7. 360安全卫士真恶心,再也不用了
  8. 离散余弦变换 DCT
  9. 百度微问答的4个坑爹之处
  10. 计算机编程语言(1)