对于day2018.5.31模拟赛的总结

又是一次模拟赛,被虐得好惨啊.

总分才392,排名虽然是第1,但还是感觉自己好菜啊.

T1(幸运数字):

定义一个6位数是lucky的,当且仅当它的前3位之和等于后3位之和,比如165912给出数x,找出最小的、大于x的幸运数字.无解输出-1.

水题,直接暴力枚举AC,代码如下:

#include<bits/stdc++.h>
  using namespace std;
const int MAX=999999;
int x;
inline void into(){
  scanf("%d",&x);
}
bool check(int x){
  int sum1,sum2;
  sum1=x%10+x%100/10+x%1000/100;
  sum2=x%10000/1000+x%100000/10000+x/100000;
  return sum1==sum2;
}
inline void work(){
  for (int i=x+1;i<=MAX;i++)
    if (check(i)) {
      printf("%d\n",i);
      return;
    }
}
inline void outo(){
}
int main(){
  into();
  work();
  outo();
  return 0;
} 

T2(好大的gcd):

有两个数组A,B,大小均为N.请在A中选择一个数x,B中选择一个数y,使得gcd(x,y)最大.如果有多组数gcd相同,找出x+y最大的.

做这道题的时候果断暴力,像个傻逼一样O(n^2)枚举,一看时间复杂度O(n^2*log(n)),只能过2000的数据了.

还能给我24分暴力分,感谢出题人.

考场代码如下:

#include<bits/stdc++.h>
  using namespace std;
const int N=500000;
int a[N+1]={0},b[N+1]={0},n;
inline void into(){
  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]);
}
int gcd(int a,int b){
  return b?gcd(b,a%b):a;
}
int max_gcd=0,max_sum=0;
inline void work1(){
  int g;
  for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++){
      g=gcd(a[i],b[j]);
      if (max_gcd<g||max_gcd==g&&max_sum<a[i]+b[j]) max_gcd=g,max_sum=a[i]+b[j];
    }
}
inline void work2(){
}
inline void outo(){
  printf("%d\n",max_sum);
}
int main(){
  into();
  if (n<=2000) work1();
  else work1(); 
  outo();
  return 0;
} 

然而正解是个很神奇的做法,直接使用两个数组A[i]和B[i]表示数i的倍数有没有出现过.

扫描二维码关注公众号,回复: 1342742 查看本文章

然后就可以枚举出gcd了.

之后O(n)扫一遍a(原数组),看看最大的gcd的倍数找到,b同理.

时间复杂度O(max*log(max)),max表示a和b数组中最大的数.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
const int N=500000;
int a[N+1]={0},b[N+1]={0},n;
bool A[N*2+1]={0},B[N*2+1]={0};
int MAX=0,gcd,sum1=0,sum2=0;
inline void into(){
  scanf("%d",&n);
  for (int i=1;i<=n;i++)
    scanf("%d",&a[i]),A[a[i]]=1,MAX=max(MAX,a[i]);
  for (int i=1;i<=n;i++)
    scanf("%d",&b[i]),B[b[i]]=1,MAX=max(MAX,b[i]);
}
inline void work(){
  A[1]=1;B[1]=1;
  for (int i=2;i<=MAX;i++)
    for (int j=i;j<=MAX;j+=i){
      if (A[j]) A[i]=1;
      if (B[j]) B[i]=1;
    }
  for (int i=MAX;i>=1;i--)
    if (A[i]&&B[i]){
      gcd=i;
      break;
    }
  for (int i=1;i<=n;i++)
    if (!(a[i]%gcd)) sum1=max(sum1,a[i]);
  for (int i=1;i<=n;i++)
    if (!(b[i]%gcd)) sum2=max(sum2,b[i]);
}
inline void outo(){
  printf("%d\n",sum1+sum2);
}
int main(){
  into();
  work();
  outo();
  return 0;
} 

T3(买买买):

蓝月商场有n件宝贝,每件宝贝有两个属性:价钱price和品牌brand。其中brand是1-5之间某个整数.每件宝贝价钱两两不同.贪玩蓝月有Q个代言人,每个代言人拍完戏之后,希望能从蓝月商场免费顺走一样宝贝.但是每个代言人有自己的喜好,代言人会有d个喜欢的品牌(1 <= d <= 5),同时他最喜欢这些品牌中,价钱第k便宜的宝贝.请你求出每个代言人最喜欢的宝贝的价钱是多少.如果不存在这件宝贝,请输出-1.

这道题好像正解思路有很多,但好像都得想一想才能写出来.

我是用了二分+前缀和的思路写出来的.

具体就是先按照价格把每件我抬起宝贝排序一下(便宜的在前),然后用一个前缀和数组num[i][j]表示到第i件物品时,种类j的宝贝有几件.

然后不管它是怎么搭配的,按照价格排序后的宝贝总是满足到第i件物品时的代言人喜好的物品数量大于第i-1件时且小于第i+1件时,满足了单调性,做到了O(n)预处理,O(log(n))查询.

至于查询是判断到第k件时有多少件物品一个代言人是喜好的,将所有这个代言人喜好的物品种类c的num[k][c]累加起来就好了,由于顶多花去O(5)的时间,是常数级别的,所以可以算一次查询只要O(log(n))的时间.

然后我还写了一个暴力,为了防止二分+前缀和写错.

听说有人是用暴力吧所有代言人可能喜好的物品种类预处理出来的,还跑得飞快,太强了.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
const int N=100000;
struct things{
  int p,b,num[6];
}a[N+1];
int n,d,q,k,l[6];
inline void into(){
  memset(a,0,sizeof(a));
  scanf("%d",&n);
  for (int i=1;i<=n;i++)
    scanf("%d",&a[i].b);
  for (int i=1;i<=n;i++)
    scanf("%d",&a[i].p);
}
bool cmp(things a,things b){
  return a.p<b.p; 
}
inline void work(){
  sort(a+1,a+1+n,cmp);
  for (int i=1;i<=n;i++){
    for (int j=1;j<=5;j++)
      a[i].num[j]=a[i-1].num[j];
    a[i].num[a[i].b]++;
  }
}
int check(int k){
  int sum=0;
  for (int i=1;i<=d;i++)
    sum+=a[k].num[l[i]];
  return sum;
}
int find(int k){
  if (check(n)<k) return -1;
  int l=1,r=n,mid;
  while (l+1<r){
    mid=l+r>>1;
    if (check(mid)>=k) r=mid;
    else l=mid;
  }
  if (check(l)>=k) return a[l].p;
  else return a[r].p;
}
inline void outo2(){
  scanf("%d",&q);
  for (int i=1;i<=q;i++){
    scanf("%d",&d);
    for (int j=1;j<=d;j++)
      scanf("%d",&l[j]);
    scanf("%d",&k);
    printf("%d\n",find(k));
  }
}
bool check1(int x){
  for (int i=1;i<=d;i++)
    if (l[i]==a[x].b) return true;
  return false;
}
int find1(int k){
  int sum=0;
  for (int i=1;i<=n;i++){
    if (check1(i)) sum++;
    if (sum==k) return a[i].p;
  }
  return -1;
}
inline void outo1(){
  scanf("%d",&q);
  for (int i=1;i<=q;i++){
    scanf("%d",&d);
    for (int j=1;j<=d;j++)
      scanf("%d",&l[j]);
    scanf("%d",&k);
    printf("%d\n",find1(k));
  }
}
int main(){
  into();
  work();
  if (n<=1000) outo1();
  else outo2();
  return 0;
}

T4(干爆字符串):

有两个仅包含小写字母的字符串x,y,长度分别为n,m.你需要修改x串中某些字符,使得新的x串和y串的最长公共子序列长度至少为k.我们将'a'...'z'对应成0…25,修改两个字符付出的代价,为它们对应的数的xor值.请问最少付出多少代价.

这道题一开始看以为是状压dp,果断放弃,之后重新又来看了一下题,数据范围n,m,k>=350,才发现这是个很简单的线性dp.

我们先设f[k][i][j],表示第1串的前i个字母与第2串的前j个字母的lcs长度为k时的最小代价.

那么我们可以推我太强了出方程(设dis[i][j]=i xor j):

若a[i]=b[j],f[k][i][j]=min(f[k][i-1][j],f[k][i][j-1],f[k-1][i-1][j-1]).

若a[i]!=b[j],f[k][i][j]=min(f[k][i-1][j],f[k][i][j-1],f[k-1][i-1][j-1]+dis[a[i]-'a'][b[j]-'a']).

那么这就可以了,但是看到数据是350为上限,说明这道题的空间会吃不消,所以让k在最外层枚举,然后用滚动数组滚掉.

那么代码如下(傻逼暴力不会写,之后直接交dp,不写部分分):

#include<bits/stdc++.h>
  using namespace std;
const int N=350;
const int INF=1000000000;
int dis[26][26]={0};
char a[N+1],b[N+1];
int n,m,k;
int f[N+1][N+1]={0},dp[N+1][N+1]={0};
int ans=INF;
int ri(){
  int x=0;
  char c=getchar();
  for (;c<'0'||c>'9';c=getchar());
  for (;c<='9'&&c>='0';c=getchar()) x=(x<<1)+(x<<3)+c-'0';
  return x;
}
char rc(){
  char c=getchar();
  for (;c<'a'||c>'z';c=getchar());
  return c;
}
inline void into(){
  for (int i=0;i<=25;i++)
    for (int j=0;j<=25;j++)
      dis[i][j]=i^j;
  scanf("%d%d%d",&n,&m,&k);
  for (int i=1;i<=n;i++)
    a[i]=rc();
  for (int j=1;j<=m;j++)
    b[j]=rc();
}
inline void work(){
  for (int i=0;i<=n;i++)
    for (int j=0;j<=m;j++)
      f[i][j]=INF;
  for (int t=1;t<=k;t++){
    for (int i=1;i<=n;i++)
      for (int j=1;j<=m;j++){
      	f[i][j]=min(f[i-1][j],f[i][j-1]);
        if (a[i]==b[j]) f[i][j]=min(f[i][j],dp[i-1][j-1]);
        else f[i][j]=min(f[i][j],dp[i-1][j-1]+dis[a[i]-'a'][b[j]-'a']);
      }
    for (int i=0;i<=n;i++)
      for (int j=0;j<=m;j++)
        dp[i][j]=f[i][j];
  }
}
inline void outo(){
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      ans=min(ans,f[i][j]);
  printf("%d\n",ans);
}
int main(){
  into();
  if (n<k||m<k) {
    printf("-1\n");
    return 0; 
  } 
  work();
  outo();
  return 0; 
} 

T5(阶乘数组):

给出长度为n的序列A.进行m次操作,有三种类型:

1.给出l,r,将区间[l,r]的Ai都加一.

2.给出l,r,询问区间[l,r]的Ai!的和,对10^9取模.

3.给出I,v,将Ai单点修改为v.

这道题一看为就知道自己肯定不会正解,直接写了暴力:

#include<bits/stdc++.h>
  using namespace std;
typedef long long LL;
const LL mod=1000000000;
const int N=100000;
int n,m;
LL a[N+1];
inline void into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;i++)
    scanf("%lld",&a[i]);
}
inline void work(){
}
LL js(LL a){
  LL sum=1LL;
  for (LL i=2LL;i<=a;i++)
    sum=sum*i%mod;
  return sum;
}
inline void outo(){
  int opt,l,r;
  LL v,sum;
  for (int i=1;i<=m;i++){
    scanf("%d",&opt);
    switch (opt){
      case 1:scanf("%d%d",&l,&r);
             for (int j=l;j<=r;j++)
               a[j]=(a[j]+1)%mod;
             break;
      case 2:scanf("%d%d",&l,&r);
             sum=0LL;
             for (int j=l;j<=r;j++)
               sum=(sum+js(a[j]))%mod;
             printf("%lld\n",sum);
             break;
      case 3:scanf("%d%lld",&l,&v);
             a[l]=v%mod;
             break;
    }
  }
}
int main(){
  into();
  work();
  outo();
  return 0;
} 

最后拿了28分,还是太菜了啊.

但是正解一看和还是很简单的,就是一棵线段树.

只不过这颗线段树利用了模数是10^9这个性质,也就是说当到达或超过40!的时候,数一定是0.

所以用线段树储存一下0~40各占了几个就行了.

但是代码太长,不愿意写了.

T6(二进制操作数组):

给定一个序列a,支持两种操作:

1.格式1 x y,修改ax为y.

2.格式2 x y,查询所有(ai+x)&y的和.

这道题一开始想到40分的很暴力的暴力,代码如下:

#include<bits/stdc++.h>
  using namespace std;
typedef long long LL;
const int N=100000;
int n,q;
int a[N+1]={0};
inline void into(){
  scanf("%d%d",&n,&q);
  for (int i=1;i<=n;i++)
    scanf("%d",&a[i]);
}
inline void work1(){
  int opt,x,y;
  LL sum; 
  for (int i=1;i<=q;i++){
    scanf("%d%d%d",&opt,&x,&y);
    if (opt==1) a[x]=y;
    else {
      sum=0LL;
      for (int j=1;j<=n;j++)
        sum+=(a[j]+x&y*1LL);
      printf("%lld\n",sum);
    }
  }
}
inline void work2(){
}
int main(){
  into();
  if (n<=5000&&q<=5000) work1();
  else work2();
  return 0;
}

之后想到x为0时的部分分的线段树,但是没来得及敲.

所以正解是x为0是的线段树的拓展,但我没听懂.

x=0是就是维护每一个区间内的数的,每个二进制位为1的数的个数.

emmm...我好菜啊.

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/80522580