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;
}
posted @ 2023-02-06 21:26  zsc985246  阅读(249)  评论(0编辑  收藏  举报