2024牛客暑期多校训练营9 - VP记录
A. Image Scaling
签到题,找出举行宽高以后直接除以它们的 \(\gcd\) 使它们互质即可。
(这道题居然会有人又 WA 又 RE,我不说是谁)
点击查看代码
#include<cstdio>
#include<cstring>
using namespace std;
const int N=505;
int n,m,x1,y1,x2,y2;
char g[N][N];
int gcd(int x,int y){return y ? gcd(y,x%y) : x;}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%s",g[i]+1);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
if(g[i][j]=='x')
{
x1=i,y1=j;
break;
}
if(x1) break;
}
for(int i=n;i>=1;i--)
{
for(int j=m;j>=1;j--)
if(g[i][j]=='x')
{
x2=i,y2=j;
break;
}
if(x2) break;
}
int h=x2-x1+1,w=y2-y1+1;
int d=gcd(w,h);
w/=d,h/=d;
for(int i=1;i<=h;i++)
{
for(int j=1;j<=w;j++)
putchar('x');
putchar('\n');
}
return 0;
}
B. Break Sequence
这道题有点意思,单独拎出来写了一篇题解,注释很详细:点击这里。
I. Interesting Numbers
题意:把一个数从正中间劈开,两边两个数都是平方数,这样的数在 \([L,R]\) 内有多少个?
赛时读题没读懂,以为是乘积或者加和,晕🤮。
运用前缀和的思想,这道题很容易转化成“\([1,R]\) 中满足条件的数的数量”-“\([1,L-1]\) 中满足条件的数的数量”。
首先这个数一定有 \(N\) 位,其中 \(N\) 为偶数。
因为这个数的左右两半是独立不互相影响的,所以可以分开考虑。
设右边界 \(R\) 的左右两半为 \(R1\) 和 \(R2\),当前数 \(X\) 的左右两半为 \(X1\) 和 \(X2\)。劈成两半后最多 \(30\) 位,刚好可以用 __int128
存(最高存 \(38\) 位)。
当 \(X1<R1\) 时,\(X2\) 无论怎么取 \(X\) 都小于 \(R\),此时:
- \(X1\) 共有 \((\lfloor\sqrt{R1-1}\rfloor+1)\) 种选择;减一是因为不能等于 \(R1\),加一是因为可以包括 \(0\)。
- \(X2\) 共有 \((\lfloor\sqrt{10^{\frac{N}{2}}-1}\rfloor+1)\) 种选择;\((10^{\frac{N}{2}}-1)\) 可以构成 \(99 \cdots 999\) 这样的结构,加一也是因为可以包括 \(0\)。
每一个 \(X1\) 和 \(X2\) 都可以任意组合,所以这时的总选择数就是两者的乘积 \((\lfloor\sqrt{R1-1}\rfloor+1)(\lfloor\sqrt{10^{\frac{N}{2}}-1}\rfloor+1)\)。
当 \(X1=R1\) 时,首先需要保证 \(R1\) 是平方数,否则这种情况不成立,此时:
- \(X1\) 仅有一种可能。
- \(X2\) 必须小于等于 \(R2\),所以此时 \(X2\) 共有 \((\lfloor\sqrt{R2}\rfloor+1)\) 种选择,加一同样是因为可以取到 \(0\)。
此时共有 \((\lfloor\sqrt{R2}\rfloor+1)\) 种选择。
上面两种(或当 \(R\) 不为平方数时仅第一种)情况相加就求出了 \([1,R]\) 中满足条件的数的数量;\([1,L-1]\) 同理,使 \(L1\) 不变,\(L2\) 减一再计算即可。
注意一下 \(L2=0\) 时的处理,我这里的处理可以看代码注释。
#include<cstdio>
using namespace std;
const int N=105;
int n;
char ls[N],rs[N];
__int128 sqrt_int128(__int128 x) //此处 sqrt(x<0)=-1
{
__int128 l=-1,r=1e15+1; //不能赋为x+1!平方会爆范围!
while(l+1<r) //l*l<=x; r*r>x
{
__int128 mid=(l+r)>>1;
if(mid*mid<=x) l=mid; //mid*mid 会爆int128!!!
else r=mid;
}
return l; //自动向下取整
}
__int128 quick_pow_int128(__int128 x,__int128 y)
{
__int128 res=1;
while(y)
{
if(y&1) res*=x;
x*=x;
y>>=1;
}
return res;
}
__int128 get_num(int n,__int128 r1,__int128 r2)
{
__int128 res=0;
res += (sqrt_int128(r1-1)+1) * (sqrt_int128(quick_pow_int128(10,n>>1)-1)+1);
res += (sqrt_int128(r1)-sqrt_int128(r1-1)) * (sqrt_int128(r2)+1); //非平方数得0,r2=-1时也得0
return res;
}
void write_int128(__int128 x)
{
if(x>9) write_int128(x/10);
putchar('0'+x%10);
}
int main()
{
scanf("%d%s%s",&n,ls+1,rs+1);
__int128 l1=0,l2=0,r1=0,r2=0;
for(int i=1; i<=n>>1;i++) l1=l1*10+(ls[i]-'0');
for(int i=(n>>1)+1; i<=n; i++) l2=l2*10+(ls[i]-'0');
for(int i=1; i<=n>>1;i++) r1=r1*10+(rs[i]-'0');
for(int i=(n>>1)+1; i<=n; i++) r2=r2*10+(rs[i]-'0');
__int128 ans=get_num(n,r1,r2)-get_num(n,l1,l2-1);
write_int128(ans);
return 0;
}
K. Kill The Monsters
贪心,每次一定优先对最大的那个进行操作二,而操作一的次数应当是剩下所有数的最大值。
所以模拟砍最大数的过程(可以用优先队列找最大数),然后统计操作次数并对所有的 \((\max a + alr)\) 取最小值(其中 \(alr\) 表示已经进行过的操作次数)。
因为没有判断全部进行操作二的可能性而 WA 了好多发,最后应当用最终的 \(alr\) 再更新一边答案。
每次操作都将其中一个除以 \(k\),所以时间复杂度为 \(O(N \log_K \max a_i )\)
还要注意特判 \(k=1\) 的情况,此时无法进行操作二,只需取最大值即可。
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int N=1e5+5;
int n;
long long k,a[N];
priority_queue<long long> pq;
int main()
{
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
pq.push(a[i]);
}
if(k==1)
{
printf("%lld\n",pq.top());
return 0;
}
long long ans=1e18,alr=0;
while(!pq.empty())
{
long long x=pq.top(); pq.pop();
ans=min(ans,x+alr);
x/=k;
if(x>0) pq.push(x);
alr++;
}
printf("%lld\n",min(ans,alr));
return 0;
}
本文采用 「CC-BY-NC 4.0」 创作共享协议,转载请注明作者及出处,禁止商业使用。
作者:Jerrycyx,原文链接:https://www.cnblogs.com/jerrycyx/p/18493111