luogu P6187 [NOI Online 提高组]最小环(民间数据)

题目传送门

//为啥题目名称叫最小环但题目要求最大环啊!!

作为一个在考场上差一点切掉这道题的蒟蒻,我还是来发个题解说一下

这道题考的东西还是蛮多的,有思维,前缀和,数论,找规律

暴力很好打,有 20 20 20分,爆搜即可

然后思考正解,对于一个 k k k,我们讲每个 a i a_{i} ai a ( i − k + n ) % n a_{(i-k+n)\%n} a(ik+n)%n建边,那么肯定会得到一些环,我们以k为距离来跳跃边,则当跳跃 s s s次且 k s ks ks n n n整除时就形成了一个环。那么我们就要求 s s s

因为 k s ks ks一定是 n n n的倍数且 s s s最小,那么 k s ks ks就是 lcm ⁡ ( k , n ) = n k gcd ⁡ ( n , k ) \operatorname{lcm}(k,n)=\frac{nk}{\gcd(n,k)} lcm(k,n)=gcd(n,k)nk,那么 s = n gcd ⁡ ( n , k ) s=\frac{n}{\gcd(n,k)} s=gcd(n,k)n

那么环上点的个数确定了,接下来要决定使环上点权值最大的方案了。

定理1:当 a + b = 2 n a+b=2n a+b=2n时, a b ab ab的最大值为 n 2 n^2 n2,且此时 a = b = n a=b=n a=b=n

证明:若 a a a不为 n n n,设 a = n − x , b = n + x a=n-x,b=n+x a=nx,b=n+x,那么ab= ( n − x ) ( n + x ) = n 2 − x 2 (n-x)(n+x)=n^2-x^2 (nx)(n+x)=n2x2,又因为 n 2 n^2 n2 x 2 x^2 x2均非负,且 x ≠ n x\neq n x=n,小于原式。证毕。

有了这个定理,那么每个数肯定要和最近的数去配对,那么应该是每一段中的数都是在排好序 a a a的中是连续的。设一个降序的排好序的环上的权值数列为 f f f f n f_n fn肯定要和 f n − 1 f_{n-1} fn1 f n − 2 f_{n-2} fn2配对,以此类推,那么问题又来了, f x , f x + 1 f_x,f_{x+1} fx,fx+1要怎么和 f x + 2 , f x + 3 f_{x+2},f_{x+3} fx+2,fx+3配对呢?

无非两种配法,最后证明出来是 f x , f x + 2 f_x,f_{x+2} fx,fx+2为一对,其余为一对,具体证明过程留给读者自行思考

那么这道题不就好做了吗? O ( n ) O(n) O(n)枚举 k k k O ( n ) O(n) O(n)枚举统计答案,询问时直接输出答案。

那么恭喜你获得了 80 80 80分的好成绩,我考试时也是栽在这里。看来我对前缀和还不太熟。

代码实现:

#include<cstdio>
#include<algorithm>
using namespace std;
long long n,m,a[200039],f[200039],s[200039],fs[200039],k,x,y,tot,pus,ans;
int main() {
//	freopen("ring.in","r",stdin);
	//freopen("ring.out","w",stdout);
	register int i,j,h;
	scanf("%lld%lld",&n,&m);
	for(i=1; i<=n; i++)scanf("%lld",&a[i]),ans+=a[i]*a[i];
	sort(a+1,a+n+1);
	f[0]=ans;
	for(i=1; i<=m; i++) {
		ans=0;
		scanf("%lld",&k);
		if(f[k]){printf("%lld\n",f[k]);continue;}
		tot=1;
		pus=1;
		do {
			pus=(pus+k-1)%n+1;
			tot++;
		} while(pus!=1);
		tot--;
		//printf("%lld\n",tot);
		for(j=1; j<=n/tot; j++) {
			ans+=a[(j-1)*tot+1]*a[(j-1)*tot+2]+a[j*tot]*a[j*tot-1];
			for(h=(j-1)*tot+3; h<=j*tot; h++)ans+=a[h]*a[h-2];
		}
		f[k]=ans;
		printf("%lld\n",ans);
	}
}

我们注意到,循环中有这样一行代码:

for(h=(j-1)*tot+3; h<=j*tot; h++)ans+=a[h]*a[h-2];

这明显是可以前缀和优化的,可以优化成 O ( n g ( n ) ) O(ng(n)) O(ng(n)) g g g为欧拉函数, g ( x ) g(x) g(x)表示 x x x的约数个数。那么我们得到代码:

#include<cstdio>
#include<algorithm>
using namespace std;
long long n,m,a[200039],f[200039],s[200039],fs[200039],k,x,y,tot,pus,ans,q[200039];
int main() {
	//freopen("ring.in","r",stdin);
	//freopen("ring.out","w",stdout);
	register int i,j,h;
	scanf("%lld%lld",&n,&m);
	for(i=1; i<=n; i++)scanf("%lld",&a[i]),ans+=a[i]*a[i];
	sort(a+1,a+n+1);
	f[0]=ans;
	for(i=3;i<=n;i++) q[i]=q[i-1]+a[i]*a[i-2];
	for(i=1; i<=m; i++) {
		ans=0;
		scanf("%lld",&k);
		if(f[k]){printf("%lld\n",f[k]);continue;}
		tot=__gcd(n,k);
		pus=n/tot;
		//printf("%lld\n",tot);
		for(j=1; j<=tot; j++) {
			ans+=a[(j-1)*pus+1]*a[(j-1)*pus+2]+a[j*pus]*a[j*pus-1]+q[j*pus]-q[(j-1)*pus+2];
		}
		f[k]=ans;
		printf("%lld\n",ans);
	}
}

这道 T G TG TG T 3 T3 T3就被我们很容易地切掉了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值