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

description

solution:

又是一道小学奥数神题
从简单情况开始考虑
\(k=1\),不妨考虑\(n=6\)的情况(其他类似)
最优情况是\(\lbrace1,3,5,6,4,2\rbrace\)
作为一名结论人,大胆猜测构造解的方式:
首先\(6\)放在中间,然后交错地再6两边放\(5,4,3,2,1\)
为什么这样是最优的呢?
感性理解的话就是尽量让大的数和大的数相乘,小的数和小的数相乘
然后dfs验证小数据后发现都是对的,那就不妨假设这个结论是正确的吧
那如果\(k=2\)呢?还是先考虑\(n=6\)的情况
可以发现位置\(2,4,6\)和位置\(1,3,5\)是两个互不干扰的环
对于每个环,我们就按照之前\(k=1\)的情况构造就行
现在要考虑的是每个环究竟放置那些数
还是那个神必东西:尽量让大的数和大的数相乘,小的数和小的数相乘
于是相当于把所有数进行排序,然后按照顺序依次加入每一个环,最后把每个环的贡献加起来就是答案了
因为这样可以保证大的数和大的数在一起
但是如何求出环的个数呢?
手动模拟+感性理解可以发现答案就是\({gcd(n,i)}\)其中\(i\)为询问
如果每次暴力做一遍显然会超时
于是我们可以预处理出\(n\)的所有约数的答案,询问的时候就可以\(O(1)\)做了

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int n,m,a[N];
ll ans[N];
inline int read()
{
	int s=0,w=1; char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
	for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
	return s*w; 
}
inline ll solve(vector<int>&v)
{
	vector<int>nx;int sz=v.size();ll anss=0;
	for(int i=0;i<sz;++i,++i)
		nx.push_back(v[i]);
	for(int i=sz-(sz&1?2:1);i>=0;--i,--i)
		nx.push_back(v[i]);
	for(int i=1;i<sz;++i)anss+=1ll*nx[i-1]*nx[i];
	return anss+1ll*nx[sz-1]*nx[0];
}
inline void work(int x)
{
	if(ans[x])return;
	vector<int>v;
	for(int i=1;i<=n;i+=x,v.clear())
	{
		for(int j=i;j<i+x;++j)v.push_back(a[j]);
		ans[x]+=solve(v);
	}
}
inline void pre()
{
	sort(a+1,a+n+1);
	int mx=sqrt(n+0.5)+1;
	for(int i=1;i<=mx;++i)
		if(n%i==0)work(i),work(n/i);
}
int gcd(int x,int y){return y?gcd(y,x%y):x;}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;++i)a[i]=read();
	pre();
	while(m--)
	{
		int k=read();
		printf("%lld\n",ans[n/gcd(k,n)]);
	}
	return 0;
}
posted @ 2020-10-24 10:37  BILL666  阅读(111)  评论(0编辑  收藏  举报