2021.08.01 院夏令营 队内训练赛第一场 杂题
⇔训练地址
⇔A - Stas and the Queue at the Buffet(源地址自⇔CF1151D)
题意:
有N个位置,每个位置i都对应有 \(a_i\) 和 \(b_i\) ,先重新排序这些位置上的 \(a_i\) 和 \(b_i\) ,使得 \(sum=(\) 这个位置左边的位置数量 \(*a+\) 这个位置左边的位置数量 \(*a)\) 最小。
思路:
对于题中所给公式,化简后找规律即可得出。
规律如下:
对于任意位置 \(j\) ,化简可得 \((a_i-b_i)*j-a_i+b_i*n\) ,发现答案只与 \((a_i-b_i)*j\) 有关,故只需将 \((a_i-b_i)\) 计算并排序,将结果小的放在前面的位置即可。
AC代码:
//A WIDA Project
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct fx{
ll x,y,w;
}a[1000005];
ll n,ans;
bool cmp(fx x,fx y) return x.w>y.w;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&a[i].x,&a[i].y);
a[i].w=a[i].x-a[i].y;
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++) ans+=a[i].w*i-a[i].x+n*a[i].y;
printf("%d\n",ans);
return 0;
}
错误次数:1次
原因:未开long long
⇔B - Dating with girls(1)(源地址自⇔HDU2578)
题意:
在给定的 \(N\) 个数据中任选(可以相同)两个数据 \(x\) 、\(y\) ,使得 \(x+y=k\) 成立。
思路:
【其一】将所有数据读入后进行一遍快排,而后使用双指针思想(开始时指针start在最前、指针end在最后,此后进行循环,若
\(a[start]+a[end]>k\) 则end指针向前一格;
\(a[start]+a[end]<k\) 则start指针向后一格;
\(a[start]+a[end]==k\) 则判定数据是否相同,相同则结果+1,不相同则结果+2。
时间复杂度: \(O(nlog_2 n)\)
空间复杂度: \(n\)
【其二(错解)】将所有数据进行一遍桶排,而后使用i进行遍历,若 \(a[i]和a[k-i]\) 同时存在,说明此时的 \(i和k-i\) 符合题意。再判断\(a[i]和a[k-i]\) 是否相同,相同则结果+1,不相同则结果+2。
时间复杂度: \(O(\frac{n}{2})\)
空间复杂度: \(k\) (这里炸了,故此方法错误)
AC代码:
//A WIDA Project
#include<bits/stdc++.h>
using namespace std;
int T,n,k,a[100005];
int main(){
scanf("%d",&T);
for(int t=1;t<=T;t++){
int sum=0,ans=0;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+1+n);//Log(n)
for(int i=2;i<=n;i++){
if(a[i-1]==a[i]){
sum++;
a[i-1]=0;
}
}
sort(a+1,a+1+n);
int s=1+sum,e=n;
while(s<=e){//n
if(a[s]+a[e]>k) e--;
else if(a[s]+a[e]<k) s++;
else{
if(a[s]==a[e]) ans++;
else ans+=2;
s++,e--;
}
}
printf("%d\n",ans);
}
return 0;
}
错误次数:4次
原因:没有去掉重复数据
原因:忽视了k的范围而直接采用了【方法二】
⇔C - Social Network (easy version)(源地址自⇔CF1234B1)
本题为E题数据减弱版,请参见E题
⇔D - Equalize Prices Again(源地址自⇔CF1234A)
题意:
对于 \(N\) 个商品,每个商品都有其本来的价格。现在要寻找一个最低的统一售价(整数),使得与之前相比不亏本。
思路:
直接求出所有商品原售价的平均值,再向上取整即可。
AC代码:
//A WIDA Project
#include<bits/stdc++.h>
using namespace std;
int T,n,x,ans;
int main(){
scanf("%d",&T);
for(int tt=1;tt<=T;tt++){
ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&x);
ans+=x;
}
int t=ans/n;
if(t*n!=ans) printf("%d\n",t+1);
else printf("%d\n",t);
}
return 0;
}
错误次数:1次
原因:忘记注掉本地读入文件
⇔E - Social Network (hard version)(源地址自⇔CF1234B2)
题意:
现在有N条短信和一次性只能显示k条短信的屏幕。对于依次发来的短信,执行下面的步骤:
若屏幕上有这条短信,则不变化;
若屏幕上没有这条短信,则这条短信会添加到屏幕顶端,如果此时屏幕上短信数量超过k条,则自动删去最后一条。
要求输出结束时屏幕上短信的数量及顺序。
思路:
使用一个标记数组 \(p[ ]\) 来标记短信是否在屏幕上,使用一个数组 \(message[ ]\) 来模拟屏幕上的变化,使用一个指针 \(start\) 来标记显示在屏幕上的最后一条消息的位置(具体应用请参见代码,代码中包含部分注释)。
【易错】由于 \(id_i\) 的范围过大,普通一维bool型数组无法开到这么大,故我们使用map数组来代替。
AC代码:
//A WIDA Project
#include<bits/stdc++.h>
using namespace std;
map<int,bool>p;
int n,k,x,start,screen,message[1000050];
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&x);
//屏幕上还留有此号码的短信,不进行操作
if(p[x]==1) continue;
//屏幕上没有此号码的短信
p[x]=1;//标记短信在屏幕上
if(screen<k) screen++;//当手机屏幕还有空余空间时,屏幕占用空间+1
else start++,p[message[start]]=0;//当手机屏幕没有空余空间了,删除最早的短信
message[start+screen]=x;//加入新的这条短信
}
cout<<screen<<"\n";
for(int i=screen+start;i>start;i--) cout<<message[i]<<" ";
return 0;
}
错误次数:【2021.08.07补题】0次
⇔F - Problem for Nazar(源地址自⇔CF1151C)
题意:
自定义数列,由 \(2^0\) 个奇数、 \(2^1\) 个偶数、 \(2^3\) 个奇数、 \(2^4\) 个奇数······构成,如下:
{1,2,4,3,5,7,9,6,8,10,12,14,16,18,20,11,13,···}
现在给出区间 \([l,r]\),求解区间内数值之和。
由于结果较大,需要将答案用 \(10^9+7\) 取模。
思路:
1.将题中所给定的“求解 \([l,r]\) 区间内符合要求的数字个数 \(N\) ”转换成“求解 \([1,r]\) 区间内符合要求的数字个数 \(N_1\) ”和“求解 \([1,l-1]\) 区间内符合要求的数字个数 \(N_2\) ”。而后再通过 \(N=N_1-N_2\) 求得答案。
2.对于每个区间右端点 \(l\) ,只需计算出其前面包含多少个奇数与偶数,而后使用高斯公式计算结果即可。
【易错】本题涉及到加减乘法与取模运算,切记取模运算只可与加法乘法共用,切勿在做减法时也取模。
【注】在代码要涉及到大数取模运算时,建议使用带空格的码风。
AC代码:
//A WIDA Project
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL MOD=1e9+7;
LL l,r,ans;
LL sum(LL x){
LL odd_len=0,even_len=0,mul=1,num=1;
while(x>mul){
if(num%2==1) odd_len=(odd_len+mul)%MOD;
else even_len=(even_len+mul)%MOD;
x=x-mul;
num++;
mul <<= 1;
}
if(num%2==1) odd_len=(odd_len+x)%MOD;
else even_len=(even_len+x)%MOD;
return (odd_len * odd_len % MOD + (even_len + 1) * even_len % MOD) % MOD;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
scanf("%lld%lld",&l,&r);
ans=sum(r)-sum(l-1);
ans=( ans + MOD ) % MOD;
printf("%lld\n",ans);
return 0;
}
错误次数:【2021.08.07补题】0次
⇔G - Double Matrix(源地址自⇔CF1162B)
题意:
给出两个 \(N*M\) 的矩阵,若这两个矩阵满足
1.每一行的元素均为从小到大排列
2.每一列的元素均为从小到大排列
若不满足以上条件,则将两个矩阵相应位置的元素互换,再进行判断。
最终满足则输出“Possible”,若不满足则输出“Impossible”。
思路:
【其一(最优解)】再建立另外两个矩阵,其每一位元素的值分别为另外俩个矩阵相应位置元素的最小值与最大值,再判断新的矩阵是否满足条件。若满足则直接输出“Possible”。
【其二(核心思路与其一一致)】(详解可参见下方AC代码2)
AC代码1:
//A WIDA project
#include<bits/stdc++.h>
using namespace std;
const int M=1005;
int n,m,a[M][M],b[M][M],c[M][M],d[M][M];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>b[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
c[i][j]=min(a[i][j],b[i][j]);
d[i][j]=max(a[i][j],b[i][j]);
}
}
for(int i=1;i<=n;i++){
for(int j=2;j<=m;j++){
if(c[i][j]<=c[i][j-1] || d[i][j]<=d[i][j-1]){
cout<<"Impossible\n";
return 0;
}
}
}
for(int j=1;j<=m;j++){
for(int i=2;i<=n;i++){
if(c[i][j]<=c[i-1][j] || d[i][j]<=d[i-1][j]){
cout<<"Impossible\n";
return 0;
}
}
}
cout<<"Possible\n";
return 0;
}
AC代码2:
//A WIDA project
#include<bits/stdc++.h>
using namespace std;
int n,m,a[1000][1000],b[1000][1000];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>b[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=2;j<=m;j++){
if(a[i][j-1]<a[i][j] && b[i][j-1]<b[i][j]) continue;//正常
else if(a[i][j-1]<b[i][j] && b[i][j-1]<a[i][j]) continue;//有错误,但可以更正
else if(b[i][j-1]<a[i][j] && a[i][j-1]<b[i][j]) continue;
else{
cout<<"Impossible";
return 0;
}
}
}
for(int j=1;j<=m;j++){
for(int i=2;i<=n;i++){
if(a[i-1][j]<a[i][j] && b[i-1][j]<b[i][j]) continue;//正常
else if(a[i-1][j]<b[i][j] && b[i-1][j]<a[i][j]) continue;//有错误,但可以更正
else if(b[i-1][j]<a[i][j] && a[i-1][j]<b[i][j]) continue;
else{
cout<<"Impossible";
return 0;
}
}
}
cout<<"Possible";
return 0;
}
错误次数:1次+【2021.08.07补题】2次
原因:忘记对列进行判断
原因:忘记建立最大值数组
原因:判断条件写错(应该为or,误写成and)
⇔H - Necklace(源地址自⇔HDU3874)
题意:
给出一个数组(包含重复元素),对于每一个给出的 \(F[l,r]\) ,输出这个区间内所有元素之和(相同元素不重复计算)。
思路:
离线线段树
AC代码:
⇔I - Cable master(源地址自⇔HDU1551)
题意:
对于给定的 \(N\) 根长度不一的电线,将它们分成 \(K\) 段长度相等的小段。输出可以分出的小段的最大值
思路:
二分答案+贪心的思想。利用二分思想,对于每一个二分求得的数据 \(mid\) ,计算其能够切割出的小段数量 \(sum\) ,并与 \(K\) 进行比较,若:
\(sum<K\) ,则说明小段长度过长,则改变右边界、缩短小段长度。
\(sum>=K\) ,则说明目前切割出的小段数量符合要求,记录此时的小段长度 \(mid\) ,并改变左边界、增长小段长度,寻找可能存在的最优解。
Hack数据:
(此数据洛谷尚未修复,HDU已修复)
Sample Input
4 11
8.04
7.43
4.57
5.39
0 0
Sample Output
2.01
AC代码1(HDU数据修复版):
#include<bits/stdc++.h>
#define LL long long
using namespace std;
long long n,k,a[100001],ans;
double t;
bool judge(LL x){
LL sum=0;
for(int i=1;i<=n;i++)sum+=a[i]/x;
return sum>=k;
}
int main(){
while(scanf("%lld%lld",&n,&k),n||k){
if(n==0 && k==0)break;
LL l=0,r=1000000050,mid;
for(int i=1;i<=n;i++) a[i]=0;
for(int i=1;i<=n;i++){
scanf("%lf",&t);
a[i]=t*1000;
}
while(l+1<r){
mid=(l+r)/2;
if(judge(mid))l=mid;
else r=mid;
}
printf("%.2lf\n",l*1.0/1000.0);
}
return 0;
}
AC代码2(洛谷数据未修复版):
#include<bits/stdc++.h>
#define LL long long
using namespace std;
long long n,k,a[100001],ans;
double t;
bool judge(LL x){
LL sum=0;
for(int i=1;i<=n;i++)sum+=a[i]/x;
return sum>=k;
}
int main(){
while(scanf("%lld%lld",&n,&k),n||k){
if(n==0 && k==0)break;
LL l=0,r=1000000050,mid;
for(int i=1;i<=n;i++) a[i]=0;
for(int i=1;i<=n;i++){
scanf("%lf",&t);
a[i]=t*100;//洛谷精度取小数点后第四位四舍五入,故此处为100
}
while(l+1<r){
mid=(l+r)/2;
if(judge(mid))l=mid;
else r=mid;
}
printf("%.2lf\n",l*1.0/100.0);//洛谷精度取小数点后第四位四舍五入,故此处为100
}
return 0;
}
错误次数:5次+【2021.08.03补题】3次
原因:二分答案模板错误
原因:精度问题(即上述两份代码100与1000的区别)
原因:未读入多组数据导致错误
原因:将double型转换为long long型时采用round函数导致隐式转换错误(精度问题)
文 / WIDA
2021.08.11成文
首发于WIDA个人博客,仅供学习讨论