CodeForces - 999D Equalize the Remainders (set+思维)

题目链接:点击这里

题目大意:
给出一个长度为 n n n 的序列 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an ,每次操作可使其中一个数 + 1 +1 +1 ,现给出一个数 m m m(保证 m ∣ n m|n mn) ,设 c i c_i ci a i m o d    m = i a_i \mod m=i aimodm=i 的数量,求使 c 0 = c 1 = . . . = c m − 1 = n m c_0=c_1=...=c_{m-1}=\frac nm c0=c1=...=cm1=mn 的最小操作数

题目分析:
一开始也想到了用 s e t set set ,也想到了存小于 n m \frac nm mn 的数的数量,就是处理从大余数变成小余数的时候越写越复杂在最后也没能调出来,最后不得不放弃重构,后来仔细想了想,发现根本不需要在意大余数变成小余数时具体变成了哪个,你只需要把当前这个大余数转移到到 s e t set set 的最小的那个元素就可以了
因此,此题的步骤就是先把不够 n m \frac nm mn 的数加入 s e t set set 然后扫一遍原序列,对大于 n m \frac nm mn 的数进行操作,找 s e t set set 种第一个比它大的数,让当前数变成他,如果找不到就把当前数变成 s e t set set 的第一个元素即可

具体细节见代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
#define inf 0x3f3f3f3f
#define int ll
using namespace std;
int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 1e6+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,m,a[maxn],c[maxn];
set<int>s; 
signed main()
{
	n = read(),m = read();
	for(int i = 1;i <= n;i++)
	{
		a[i] = read();
		c[a[i]%m]++;
	}
	int maxx = n/m,ans = 0;
	for(int i = 0;i < m;i++) //把不够的都加入set 
		if(c[i] < maxx) s.insert(i);
	for(int i = 1;i <= n;i++)
	{
		if(c[a[i]%m] <= maxx) continue;
		c[a[i]%m]--; auto pos = s.lower_bound(a[i]%m);
		if(pos != s.end())
		{
			int p = *pos;
			c[p]++;
			if(c[p] == maxx) s.erase(pos);
			ans += p-a[i]%m;
			a[i] += p-a[i]%m;
		}
		else {//在另一半转移过来 
			int rem = m-a[i]%m;
			pos = s.begin();
			int p = *pos; 
			c[p]++;
			if(c[p] == maxx) s.erase(pos);
			ans += p+rem;
			a[i] += p+rem;
		}
	} 
	printf("%lld\n",ans);
	for(int i = 1;i <= n;i++)
		printf("%lld%c",a[i],i==n ? '\n' : ' ');
	return 0;
}

你可能感兴趣的:(思维,规律)