Codeforces Round #601 (Div. 1) A-B2 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17096620.html ,转载请注明出处。
传送门
Codeforces Round #601 (Div. 1)
A. Feeding Chicken
题目大意
有一个 \(n \times m\) 的网格,上面的每个数是 \(0\) 或 \(1\)。求一种将网格划分为 \(k\) 个连通块的方案,使得块中 \(1\) 的个数极差最小。
\(n,m \le 100,k \le 62\)。
思路
蛇形遍历整个网格可以保证连通性,块内的 \(1\) 的个数可以提前计算,直接模拟即可。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=100+10;
using namespace std;
ll n,m,k;
ll a[N][N];
char b[N][N];//答案数组
//题目要求的神奇输出格式
char c[111]={'0','1','2','3','4','5','6','7','8','9',
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
void mian(){
ll cnt=0;
ll s=0;//1的总数
scanf("%lld",&n);
scanf("%lld",&m);
scanf("%lld",&k);
For(i,1,n){
char str[111];
scanf("%s",str+1);
For(j,1,m){
if(str[j]=='R')a[i][j]=1;
else a[i][j]=0;
s+=a[i][j];
}
}
ll tot=s/k;//每个连通块1的个数
ll id=k-s+tot*k;//大于等于它的编号都需要多分一个1
ll now=0;//已经划分的1的个数
For(x,1,n){
if(x&1){
For(y,1,m){
now+=a[x][y];
b[x][y]=c[cnt];
if(now==tot+(cnt>=id)&&cnt<k-1){//注意不要分多了,保证cnt<=k
cnt++;
now=0;
}
}
}else{
Rep(y,m,1){
now+=a[x][y];
b[x][y]=c[cnt];
if(now==tot+(cnt>=id)&&cnt<k-1){
cnt++;
now=0;
}
}
}
}
For(i,1,n){
For(j,1,m){
putchar(b[i][j]);
}
printf("\n");
}
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
B. Send Boxes to Alice
题目大意
有 \(n\) 个盒子,分别放了 \(a_i\) 个物品。一次操作可以将一个物品移到相邻的盒子里。求使所有非空盒子物品数的最小公约数不为 \(1\) 的最小操作次数。
在 Easy Version 中,\(n \le 10^5,0 \le a_i \le 1\)。
在 Hard Version 中,\(n \le 10^6,0 \le a_i \le 10^6\)。
思路
对于 B1,发现 \(a_i\) 只有可能是 \(0\) 或 \(1\)。
当然,如果 \(1\) 的个数少于 \(2\),肯定无解。
考虑直接枚举它们的最小公约数 \(k\),然后将每 \(k\) 个 \(1\) 移动到一起。
可以发现,最终每一组都会移到最中间的 \(1\) 的位置。(将 \(1\) 看成数轴上的点,抽象成中位数定理的模型)
这样 B1 就做完了。
对于 B2,我们发现还是可以枚举它们的最小公约数 \(k\),但是我们计算答案的方式需要改变。
考虑将数组作前缀和。可以发现,一次操作就变为了将前缀和数组中的一个数 \(+1\) 或 \(-1\)。当然盒子中物品的数量不可能是负数,所以还需要保证 \(s\) 数组单调递增。
也就是说,我们最后需要使每个 \(s_i\) 都是 \(k\) 的倍数。
我们发现,当 \(s\) 数组具有单调性时,如果让每个 \(s_i\) 变成离它最近的 \(k\) 的倍数,形成的新数组同样满足单调性。同时,由于我们让它成为了最近的倍数,所以一定保证操作数最小。
这样 B2 就搞定了。
代码实现
这里就放 B2 的代码好了。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
ll n,m,k;
ll a[N];
ll s[N];
ll calc(ll x){//计算答案
ll res=0;
For(i,1,n){
res+=min(s[i]%x,x-s[i]%x);//离它最近的倍数
}
return res;
}
void mian(){
ll ans=5e18;//开大一点,在B1中最大是3e9,B2中是3e16
scanf("%lld",&n);
For(i,1,n){
scanf("%lld",&a[i]);
s[i]=s[i-1]+a[i];//前缀和
}
if(s[n]<=1){//总共不到2个物品
printf("-1\n");
return;
}
ll t=s[n];
for(ll i=2;i*i<=t;++i){
if(t%i==0){
while(t%i==0)t/=i;
ans=min(ans,calc(i));
}
}
if(t!=1){
ans=min(ans,calc(t));
}
printf("%lld\n",ans);
}
int main(){
int T=1;
// scanf("%d",&T);
while(T--)mian();
return 0;
}