[NOI Online #1 提高组]最小环

传送


这道题的题目给了一个很有用的提示。


他都说环了,所以我们应该往环上想:所有相距为\(k\)的点构成了一个封闭的环。
准确来说,原来的\(n\)个数被分成了\(gcd(k,n)\)个环,每个环的长度是\(\frac{n}{gcd(k, n)}\)。


那么怎么能让乘积和最大呢?换句话说,就是让乘积不平均。
所以我们尽量把大的放一块,小的放另一块相乘。
证明方法可以用贪心的“微扰”和数学归纳法来证明,这里就不具体懒得证明了。


当我们把大数放在一个环里后,也要按同样的方法尽量把大数和大数放在一块。注意,这并不是单纯的从大到小排序,比如一组数据5 4 3 2 1,这么排的话结果是45,但序列1 3 5 4 2能取到48.
所以我们要用贪心的策略:每一次选当前序列前后最大的数并放在他旁边。这个也可以\(O(n)\)完成。


不过这么做的复杂度是\(O(n^2)\),但\(gcd(k,n)\)的个数不足\(\sqrt{n}\),所以加一个记忆化就过了,复杂度\(O(n\sqrt{n})\),大概是8e7,不过实测还是很快的。


上代码:

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<queue>
#include<assert.h>
#include<ctime>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 2e5 + 5;
In ll read()
{
	ll ans = 0;
	char ch = getchar(), las = ' ';
	while(!isdigit(ch)) las = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(las == '-') ans = -ans;
	return ans;
}
In void write(ll x)
{
	if(x < 0) x = -x, putchar('-');
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}
In void MYFILE()
{
#ifndef mrclr
	freopen(".in", "r", stdin);
	freopen(".out", "w", stdout);
#endif
}

int n, m;
ll a[maxn];

In int gcd(int a, int b) {return b ? gcd(b, a % b) : a;}

ll ans[maxn];
In ll solve(int x)
{
	x = n / x;
	ll ret = 0;
	for(int i = 1; i <= n; i += x)
	{
		ll la = a[i], lb = a[i + 1];
		ll sum = a[i] * a[i + 1];
		for(int j = 2; j < x; ++j)
		{
			if(la < lb) swap(la, lb);
			sum += la * a[i + j], la = a[i + j];
		}
		ret += sum + la * lb;
	}
	return ret;
}

int main()
{
//	MYFILE();
	n = read(), m = read();
	for(int i = 1; i <= n; ++i) a[i] = read();
	sort(a + 1, a + n + 1), reverse(a + 1, a + n + 1);
	ll tp = 0;
	for(int i = 1; i <= n; ++i) tp += a[i] * a[i];
	ans[0] = tp;
	for(int i = 1; i <= m; ++i)
	{
		int x = read(), g = x ? gcd(x, n) : 0;
		write(ans[g] ? ans[g] : ans[g] = solve(g)), enter;
	}
	return 0;
}
上一篇:project_online


下一篇:[NOI Online #1 提高组]冒泡排序