又是一次模拟赛,被虐得好惨啊.
总分才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的倍数有没有出现过.
然后就可以枚举出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...我好菜啊.