热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

牛客算法入门竞赛笔记1

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

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

# 模拟,枚举与贪心

字符串 (nowcoder.com)

 

尺取法(说实话这可能是我第一次见到这个做法,或者第一次知道它的学名),正常暴力想法应该是枚举两个端点,但是这里的右端点其实不用后退,想一下就知道了,因此只要两个端点差速右移就是O(n)的复杂度了

 #include
 using namespace std;
 int main(){
  mapx;
  string a;
  cin>>a;
  int l=0,r=0;
  for(int i=0;i1){
  x[a[i]]--;
  i++;
  }
  if(x.size()==26){
  ans=min(ans,r-i+1);
  }
  if(r==a.size()-1){
  break;
  }
  }
  cout<  return 0;
 }

丢手绢 (nowcoder.com)

尺取+1,但是我第一发wa了,90分,因为每一次移动要考虑的不仅是逆时针的最大值,还有顺时针的最大值。

 #include
 using namespace std;
 #define ll long long
 ll x[100001];
 int main(){
  int n;
  cin>>n;
  ll sum=0;
  for(int i=0;i  cin>>x[i];
  sum+=x[i];
  }
  ll l=0,r=0;
  ll now=0,ans=0;
  while(1){
  while(now*2<=sum&&r<=n-1){
  ans=max(now,ans);
  now+=x[r];
  r++;
  }
  while(now*2>sum){
  ans=max(ans,sum-now);//第一发没加这一句
  now-=x[l];
  l++;
  }
  if(r==n){
  break;
  }
  }
  cout<

Flip Game (nowcoder.com)

黑白棋问题:

1-每个棋子最多只能翻一次

2-我们枚举第一行的翻法(16种),然后递推出第二第三行的翻法(上一行哪个没翻第二行就翻下面那个),如果第四行都翻过来了,就成功,否则失败

题解里有个位运算的做法贼牛逼,把每一行都看成二进制串,假设我按第一个和第三个,那么对应的按操作的二进制码就是1010,现在我一共要对三个地方取反,第一:本位取反,直接让原码和操作码异或,第二:每个按操作的左边取反,操作码左移一位再异或,第三:每个按操作的右边取反,操作码右移一位再异或。

异或就是不进位的二进制加(好记)

看课程里老师调了半天,而我一眼就看出她那里有问题,给我急得()

 #include
 #include
 #define INF 0x3f3f3f3f
 using namespace std;
 ​
 int a[5],b[5];
 int ans=INF;
 ​
 int count_1_num(int x){
  int num=0;
  while(x>=1){
  num+=x%2;
  x/=2;
  }
  return num;
 }
 ​
 int fun(int*x){
  int t[5];
     //枚举16种第一行的情况
  for(int i=0;i<16;i++){
         //计算有几个1,就代表翻转了几次
  int num=count_1_num(i);
  for(int i=0;i<4;i++){
  t[i]=x[i];
  }
  t[0]=(t[0]^(i)^(i<<1)^(i>>1));
  t[1]=(t[1]^(i));
  t[0]=(t[0]&15);
  for(int k=1;k<4;k++){
  num+=count_1_num(t[k-1]);
  t[k]=(t[k]^(t[k-1])^(t[k-1]<<1)^(t[k-1]>>1));
  t[k+1]=(t[k+1]^(t[k-1]));
  //把有可能溢出的第五位变成0(这步是我没想到的)
  t[k]=t[k]&15;
  }
  if(t[3]==0){
  ans=min(num,ans);
  }
  }
 }
 ​
 int main(){
  for(int i=0;i<4;i++){
  for(int k=0;k<4;k++){
  char c;
  scanf(" %c",&c);
  if(c=='b'){
  //把a[i]的第k+1位变成1
  a[i]=a[i] | (1<  }else{
  b[i]=b[i] | (1<  }
  }
  }
  fun(a);
  fun(b);
     //第一发忘了加impossible居然0分是我没想到的,难道说。。。
  if(ans==INF){
  cout<<"Impossible"<  return 0;
  }
  cout<



 

 

震惊我一整年的做法,原题数据范围很小,可以把所有学号相加减去来的,但是如果数据范围非常大,学号之和已经超过long long了,就不能这样做了

于是,它来了,位运算:加减相消的原理和两次异或不变的原理是互通的,所以我们可以把所有的学号异或起来,然后再异或掉来的人的学号,剩下的就是没来的。

艹,太帅了,我直接拜发明这个算法的人为师,orz

再升级一下,如果两个人没来呢?

先和上面一样操作一遍得到两个没来的人的学号异或值,找到一位为1的,表明这两个人在这一位上不同,就可以将原数分为两块,每一块都是一个人没来的问题。

ohhhhhh,130没白花,长见识了。

Problem - 333E - Codeforces

 

这题听他讲好像没什么,一搜2500,妈耶,我到底在学什么,我一个1300分的人也配想2500的题?

思路很巧妙,我们枚举两两点之间的距离,将其从大到小排序,从长边到短边,构成的第一个三角形的最短边长的一半就是答案

但是。我们怎么判断什么时候构成一个三角形呢,最质朴的想法就是我每走过一条边都记录下每个点对应的已经检验过的邻点,如果出现一条边AC,A已经走过B,C也已经走过B,就成了,但是如果正常记录遍历的话必定会超时,怎么办呢? 震惊我两整年的想法来了,c++里有一个bitset类,就是一个很长的二进制串,我们存走过的点时,给它转化成对应的bitset位的值,然后每条新边来的时候判断一下两个端点的bitset的位与,如果不为零,就说明他们之前经过过同一个点,齐活!

我居然自己写出来了,艹,牛逼,去补一下bitset的接口(零碎点那个文档里)

#include
 using namespace std;
 pairx[3001];
 struct node{
  int x,y;
  double dis;
 };
 node t[10000001];
 bitset<3001>ans[3001];
 ​
 double distance(pairx,pairy){
  return sqrt((x.first-y.first)*(x.first-y.first)+(x.second-y.second)*(x.second-y.second));
 }
 ​
 bool com(node a,node b){
  return a.dis>b.dis;
 }
 ​
 ​
 int main(){
  int n;
  cin>>n;
  ans[0].reset();
  for(int i=1;i<=n;i++){
  cin>>x[i].first>>x[i].second;
  ans[i].reset();
  }
  int num=0;
  for(int i=1;i<=n;i++){
  for(int j=i+1;j<=n;j++){
  t[num].x=i;
  t[num].y=j;
  t[num].dis=distance(x[i],x[j]);
  num++;
  }
  }
  sort(t,t+num,com);
  for(int i=0;i  int x=t[i].x;
  int y=t[i].y;
  if((ans[x]&ans[y])!=0){
  printf("%.8lf",t[i].dis/2);
  break;
  }
  ans[x].set(y,1);
  ans[y].set(x,1);
 
  }
  return 0;
 }

月月查华华的手机 (nowcoder.com)

题意:判断序列b是否为a的子序列(不连续)

这题我看到的时候居然想上kmp,艹,绝了,kmp是匹配连续子串,这个是不连续序列,其实就是双指针,但是,这题数据范围很大,所以如过对每个目标串都用一次双指针,就t了。

因此这里用了一个预处理,记录下每个位置,其后面每个字母第一次出现的位置,从后往前扫一遍就有了,感觉有点像ccpc网络赛的那个字符串dp的签到题(一模一样好伐)。

 #include
 using namespace std;
 ​
 int x[1000001][30];
 int last[30];
 ​
 int main(){
  string a;
  cin>>a;
  memset(last,-1,sizeof(last));
  for(int i=a.size()-1;i>=0;i--){
  for(int k=0;k<26;k++){
  x[i][k]=last[k];
  }
  last[a[i]-'a']=i;
  }
  int t;
  cin>>t;
  while(t--){
  string b;
  cin>>b;
  int pos=last[b[0]-'a'];
  int beg=pos;
  if(pos<0){
  cout<<"No"<  continue;
  }
  int flag=1;
  for(int i=1;i  pos=x[pos][b[i]-'a'];
  if(pos<0){
 // pos=x[beg][b[0]-'a'];//这里就是因为没想明白串和序列,想回退
 // beg=pos;
  if(pos<0){
  flag=0;
  cout<<"No"<  break;
  }
  //i=0;
  }
  }
  if(flag==1){
  cout<<"Yes"<  }
  }
  return 0;
 }

Problem - 484A - Codeforces

题意:找l-r内二进制1数量最多的数

位运算+贪心,因为数据范围太大,必不可能遍历,所以考虑贪心,把左右边界都变为二进制,把左边界为0的二进制位一个个改成1,看是否还小于右边界即可。tql,我自己想绝对想不出来,但是实现是我自己写的,我也算是稍微了解一些位运算了吧(

 #include
 using namespace std;
 #define ll long long
 int main(){
  int t;
  cin>>t;
  while(t--){
  ll l,r;
  cin>>l>>r;
  ll ans=l;
  for(ll i=1;i<=r;i<<=1){
  if((l&i)==0){
  ans+=i;
  if(ans>r){
  ans-=i;
  break;
  }
  }
  }
  cout<

兔子的区间密码 (nowcoder.com)

题意:求l-r内两个数的异或的最大值

找到l,r的第一个不一样的二进制位,从这位开始后面可以保证一个数全是1,另一个全是0.

 #include
 using namespace std;
 #define ll long long
 int main(){
  int t;
  scanf("%d",&t);
  while(t--){
  ll a,b;
  scanf("%lld %lld",&a,&b);
  int i=63;
  for(;i>=0;i--){
  if((a>>i)!=(b>>i)){
  break;
  }
  }
  printf("%lld\n",(1LL<  }
  return 0;
 }

位运算:一位一位去考虑


递归与分治

表达式计算4 (nowcoder.com)

题意就跟标题一样,计算表达式,当时正好数据结构也布置了一个类似的作业,思路是每一次都找到运算级最低的那个符号,将表达式分为两个部分,分别计算。

 #include
 using namespace std;
 string a;
 //string转int
 int num(int l,int r){
  int ans=0;
  for(int i=l;i<=r;i++){
  ans*=10;
  ans+=a[i]-'0';
  }
  return ans;
 }
 //计算l到r的计算式
 int calc(int l,int r){
  int pos1=-1,pos2=-1,pos3=-1;
  int cnt=0;
  //找到不在括号里的优先级最低的运算符的位置
  for(int i=l;i<=r;i++){
  if(a[i]=='('){
  cnt++;
  }
  if(a[i]==')'){
  cnt--;
  }
  if(cnt==0){
  if(a[i]=='+'||a[i]=='-'){
  pos1=i;
  }
  if(a[i]=='*'||a[i]=='/'){
  pos2=i;
  }
  if(a[i]=='^'){
  pos3=i;
  }
  }
  }
  //如果没找到运算符
  if(pos1==-1&&pos2==-1&&pos3==-1){
  //要么是被括号整体括起来了
  //注:后面两个else if是为了排除多余的括号
  if(cnt==0&&a[l]=='('){
  return calc(l+1,r-1);
  }else if(cnt<0&&a[l]=='('){
  return calc(l,r-1);
  }else if(cnt>0&&a[r]==')'){
  return calc(l+1,r);
  }
  //要么是就是一个数字
  return num(l,r);
  }
  //+ -优先级最低,先判断
  if(pos1!=-1){
  if(a[pos1]=='+'){
  return calc(l,pos1-1)+calc(pos1+1,r);
  }else{
  return calc(l,pos1-1)-calc(pos1+1,r);
  }
  }
  //然后是* /
  if(pos2!=-1){
  if(a[pos2]=='*'){
  return calc(l,pos2-1)*calc(pos2+1,r);
  }else{
  return calc(l,pos2-1)/calc(pos2+1,r);
  }
  }
  //最后是乘方
  if(pos3!=-1){
  return pow(calc(l,pos3-1),calc(pos3+1,r));
  }
 }
 ​
 int main(){
  cin>>a;
  cout<  return 0;
 }

1027-kotori和糖果_2021秋季算法入门班第二章习题:递归、分治 (nowcoder.com)

题意:共n堆糖果,一堆一个,合并两堆数量不一样的花费1,求最少总花费

这题本来是个很简单的记忆化搜索,但是我没想清楚应该返回什么,我本来想着返回两端的长度,但是后来转念一想,不对啊,有了n两端长度不是都有了吗,所以应该返回的是形成这两端需要的操作数。

#include
using namespace std;
typedef long long ll;
map mp;
ll getans(ll n){
if(n==0) return 0;
if(mp.count(n)) return mp[n];
if(n&1){
mp[n]=2*getans(n/2);
}else{
mp[n]=getans(n/2)+getans(n/2-1)+1;
}
return mp[n];
}
int main(){
ll t;
scanf("%lld",&t);
while(t--){
ll n;
scanf("%lld",&n);
if(n==0)puts("0");
else printf("%lld\n",getans(n-1));
}
}


二分、三分、01分数规划

艹,我记得我写完这一部分的了,但是好像关电脑前忘了保存,然后就没了,心态爆炸,再写一遍,呜呜呜

小咪买东西 (nowcoder.com)

01分数规划经典题目,题意:给n个物品,每一个物品都有一个价值和花费,求买k个的最大总价值(val)和总花费之比(cos)

思路:我们假设这个最大比值是x,那么比x小的比值一定也存在这样的方案,比x大的比值一定不存在这样的方案,那么我们就可以考虑二分,对于一个比值num来说,如果可行,一定存在一个方案使得val/cos>=num-->val-numcos>=0;因此我们在判断的时候把所有val-numcost最大的k个值算在内,如果这样都不行那就是真的不行。

#include
using namespace std;
#define ll long long
struct node{
ll c,v,mar;
};
ll n,m;
node x[10001];
bool com(node a,node b){
return a.mar>b.mar;
}
bool fun(ll a){
ll num=0;
for(ll i=0;i x[i].mar=x[i].v-a*x[i].c;
}
sort(x,x+n,com);
ll sum=0;
for(ll i=0;i sum+=x[i].mar;
}
if(sum<0){
return false;
}else{
return true;
}
}
int main(){
ll t;
cin>>t;
while(t--){
cin>>n>>m;
for(ll i=0;i cin>>x[i].c>>x[i].v;
}
ll l=0,r=1e8+1;
ll mid=0;
while(l mid=(l+r+1)/2;
if(fun(mid)){
l=mid;
}else{
r=mid-1;
}
}
cout< }
return 0;
}

gpa (nowcoder.com)

和上一题差不多,就是把最大值公式变了一下,我们一样做

#include
using namespace std;
#define INF 0x3f3f3f3f
#define ll double
struct node{
double a,b,tem;
};
node x[100001];
int n,m;
bool com(node a,node b){
return a.tem}
bool fun(ll a){
for(int i=0;i x[i].tem=x[i].a*(x[i].b-a);
}
sort(x,x+n,com);
for(int i=0;i if(x[i].tem<0){
x[i].tem=-INF;
}
}
ll sum=0;
for(int i=0;i if(x[i].tem==-INF){
continue;
}
sum+=x[i].tem;
}
if(sum<0){
return false;
}else{
return true;
}
}
int main(){
cin>>n>>m;
for(int i=0;i cin>>x[i].a;
}
for(int i=0;i cin>>x[i].b;
}
double l=0,r=1e11+1;
while(r-l>1e-6){//这里答案是小数,所以我们用控制精度的方法
double mid=(l+r)/2;
if(fun(mid)){
l=mid;
}else{
r=mid;
}
}
printf("%.6lf\n",l);
return 0;
}


堆栈、队列、单调栈、单调队列

[NOIP2016]蚯蚓 (nowcoder.com)

题意:给你n个数,每次会将一个数分割成比例为定值的两个数,求m次分割怎么让每个数尽可能小,每个数还会随时间增长一定值。

这题本来觉得没啥难的,优先队列嘛,谁不会啊(),但是看了眼数据范围,完蛋,会t,没办法,只能听雨巨讲了。

思路真的很巧妙,假设我们将最初的序列排序,拆分之后将两段分别放进两个队列里面,因为我们每次拆分的一定都比上一次小(或者一样)因此拆分后的两段一定也比先前的两段小,所以我们后放入队列的一定比先放入的小,这样就保证了有序,每次选择最大的时候只要去找三个队列的头即可。tql,未曾设想的道路。

但是这个输出要求就很恶心,我好不容易写完,你非要在输出上折磨我是什么意思

#include
#include
#include
using namespace std;
#define ll long long
int main() {
queueq[3];
int x[100001];
ll n, m, Q, u, v, t;
cin >> n >> m >> Q >> u >> v >> t;
double p = (double)u / v;
for (int i = 0; i cin >> x[i];
}
sort(x, x + n, greater());
for (int i = 0; i q[0].push(x[i]);
}
int now = 0;
while (now int mn = -0x3f3f3f3f, maxindex=-1;
for (int i = 0; i <3; i++) {
if (!q[i].empty() && q[i].front() > mn) {
mn = q[i].front();
maxindex = i;
}
}
if (maxindex==-1) {
break;
}
mn += now * Q;
q[maxindex].pop();
q[1].push((int)(u * mn/v) - now * Q-Q);
q[2].push(mn - (int)(u * mn/v) - now * Q-Q);

if ((now+1) % t == 0) {
cout < }
now++;
}
cout < now=1;
while (!q[0].empty() || !q[1].empty() || !q[2].empty()) {
int mn = -0x3f3f3f3f, maxindex=-1;
for (int i = 0; i <3; i++) {
if (!q[i].empty() && q[i].front() > mn) {
mn = q[i].front();
maxindex = i;
}
}
if (maxindex==-1) {
break;
}
if(now%t==0){
cout < }
now++;
q[maxindex].pop();
}
return 0;
}

Sliding Window (nowcoder.com)

 

动态维护一个在i-k~i之间可能成为答案的队列。

以最大值为例,如果后一个比前一个大,那么前一个就失去了成为答案的机会,就可以出队了,反之就不出队,但是当这个值的位置比区间的左端点还小的时候也要出队,本来我想用队列存数据的,但是雨巨点醒了我,存数据的话还要直到下标,所以我们直接把下标存起来,到原数组里找对应的数据即可。

第一次用对拍,有点爽,第一次交的时候wa在了长度为1的时候。

#include
using namespace std;
int x[1000011];
int main(){
// freopen("a.in","r",stdin);
// freopen("std.out","w",stdout);
int n,k;
cin>>n>>k;
for(int i=0;i cin>>x[i];
}
if(n==1){
cout< return 0;
}
dequeq;
// q.push_back(0);第一次wa在这.以后别tm提前插入行不行,对拍的时候是很爽,但是写起来很麻烦
for(int i=0;i while(!q.empty()&&x[q.back()]>x[i]){
q.pop_back();
}
q.push_back(i);
if(q.front()<=i-k){
q.pop_front();
}
if(i+1>=k){
cout< }
}
cout< q.clear();
for(int i=0;i while(!q.empty()&&x[q.back()] q.pop_back();
}
q.push_back(i);
if(q.front()<=i-k){
q.pop_front();
}
if(i+1>=k){
cout< }
}
return 0;
}

Largest Rectangle in a Histogram (nowcoder.com)

单调栈的板子题,所谓单调栈就是我们维护一个单调的栈,每当有新数据进来时,如果它破坏了原有的单调性,我们就一直出栈直到单调为止。

题意:给你一串高度不等高的等宽柱子,求能够在里面划出的最大的矩形的面积。

思路:我们存下每个柱子往左能够延伸的最大长度,如果新加的柱子比之前的高,那它的最大宽度就是1,如果新加的柱子比之前的矮,那我们在出栈的过程中把每一个出栈的柱子的往左延伸的最大长度相加得到的就是这个新柱子的最大长度,当然,如果一个柱子出栈了,就说明它不可能再向右延申了,这时我们用高度×它能向左延申的最大长度就是一个可能的最大面积。

#include
using namespace std;
#define ll long long
int x[100001];
int num[100001];//记录每个柱子能够向左延伸的最大长度
int main(){
int n;
while(cin>>n&&n){
for(int i=1;i<=n;i++){
cin>>x[i];
}
stackstk;
ll ans=0;
x[++n]=0;
for(int i=1;i<=n+1;i++){
ll temp=0;
while(!stk.empty()&&x[i]<=x[stk.top()]){
temp+=num[stk.top()];
ans=max(ans,temp*x[stk.top()]);
stk.pop();
}
stk.push(i);
num[stk.top()]=temp+1;
}
cout< }
return 0;
}



推荐阅读
  • 来自FallDream的博客,未经允许,请勿转载,谢谢。一天一套noi简直了.昨天勉强做完了noi2011今天教练又丢出来一套noi ... [详细]
  • 本文探讨了如何利用数组来构建二叉树,并介绍了通过队列实现的二叉树层次遍历方法。通过具体的C++代码示例,详细说明了构建及打印二叉树的过程。 ... [详细]
  • 2022年4月15日的算法练习题,包括最长公共子序列和线段树的应用。 ... [详细]
  • 本文详细介绍了Oracle RMAN中的增量备份机制,重点解析了差异增量和累积增量备份的概念及其在不同Oracle版本中的实现。通过对比两种备份方式的特点,帮助读者选择合适的备份策略。 ... [详细]
  • 本文详细探讨了如何处理包含多种分隔符的字符串分割问题,并提供了一个高效的C++实现方案。 ... [详细]
  • 本题要求计算从起点到终点所有最短路径的总权重,使用SPFA算法进行求解。 ... [详细]
  • 题面:P3178[HAOI2015]树上操作好像其他人都嫌这道题太容易了懒得讲,好吧那我讲。题解:第一个操作和第二个操作本质上是一样的&# ... [详细]
  • HDU 2537 键盘输入处理
    题目描述了一个名叫Pirates的男孩想要开发一款键盘输入软件,遇到了大小写字母判断的问题。本文提供了该问题的解决方案及实现方法。 ... [详细]
  • 深入解析轻量级数据库 SQL Server Express LocalDB
    本文详细介绍了 SQL Server Express LocalDB,这是一种轻量级的本地 T-SQL 数据库解决方案,特别适合开发环境使用。文章还探讨了 LocalDB 与其他轻量级数据库的对比,并提供了安装和连接 LocalDB 的步骤。 ... [详细]
  • A1166 峰会区域安排问题(25分)PAT甲级 C++满分解析【图论】
    峰会是指国家元首或政府首脑之间的会议。合理安排峰会的休息区是一项复杂的工作,理想的情况是邀请的每位领导人都是彼此的直接朋友。 ... [详细]
  • 本文详细解析 Skynet 的启动流程,包括配置文件的读取、环境变量的设置、主要线程的启动(如 timer、socket、monitor 和 worker 线程),以及消息队列的实现机制。 ... [详细]
  • 深入解析C++ Atomic编程中的内存顺序
    在多线程环境中,为了防止多个线程同时修改同一数据导致的竞争条件,通常会使用内核级同步对象,如事件、互斥锁和信号量等。然而,这些方法往往伴随着高昂的上下文切换成本。本文将探讨如何利用C++11中的原子操作和内存顺序来优化多线程编程,减少不必要的开销。 ... [详细]
  • ED Tree HDU4812 点分治+逆元
    这道题非常巧妙!!!我们进行点分治的时候,算出当前子节点的所有子树中的节点,到当前节点节点的儿子节点的距离,如下图意思就是当前节点的红色节点,我们要求出红色节点的儿子节点绿色节点, ... [详细]
  • 今天老师上课讲解的很好,特意记录下来便于以后复习。多态的简单理解*1.什么是多态性?*(1)同一个动作与不同的对象产生不同的行为*(2)多态指的 ... [详细]
  • 本文将作为我硕士论文的一部分,但鉴于其内容的独特性和趣味性,决定单独发布。文中将定义一些皮亚诺公理,并介绍如何使用这些公理进行等式替换,以证明定理。 ... [详细]
author-avatar
半夏✔
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有