构图

题意
n个点,有点权,正数或负数,可以连边,边的代价为两端点权的积,但要满足最终的图每个点度数大于0,且一个正数点不能连超过1个负数点
数据范围
3e4,可以到2e5

样例
input
4
-1 2 -3 4

output
-15

(-3向其他连边)


显然不会成环,有环必有正数,因此答案一定是生成森林


如果仅有正数或仅有负数,那么肯定是最大连最小,次大连次小……
既有正数又有负数的话
将正数负数分别从大到小连边,
连边和下图类似:
1
这样就可以用FFT优化啦

#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 65536
#define ll long long
#define db double
using namespace std;
int n,rev[N],a[N],b[N];
ll sm1[N],sm2[N],A[N],B[N],C[N],D[N],E[N],F[N];
struct complex{
	db a,b;complex(db _a=0,db _b=0){a=_a,b=_b;}
}w[N];
complex operator+(complex a,complex b){return complex(a.a+b.a,a.b+b.b);}
complex operator-(complex a,complex b){return complex(a.a-b.a,a.b-b.b);}
complex operator*(complex a,complex b){return complex(a.a*b.a-a.b*b.b,a.a*b.b+a.b*b.a);}
void fft(complex *a){
	for(int i=1;i<N;++i)if(rev[i]<i)swap(a[i],a[rev[i]]);complex *s,*t,T,*W;
	for(int h=1,m=2,i,j,g=N>>1;h<N;h=m,m<<=1,g>>=1)for(i=0,s=a;i<N;i+=m,s=t)
		for(W=w,t=s+h,j=0;j<h;++j)T=*t**W,*t=*s-T,*s=*s+T,++s,++t,W+=g;
}
void ifft(complex *a){
	for(int i=1;i<N>>1;++i)swap(a[i],a[N-i]);fft(a);
	for(int i=0;i<N;++i)a[i]=complex(a[i].a/N,a[i].b/N);
}
void mult(ll *a,ll *b,ll *c){
	static complex A[N],B[N],C[N],D[N];
	for(int i=0;i<N;++i){
		int sig=a[i]<0?-1:1;a[i]=sig*a[i];
		A[i]=complex((a[i]>>12)*sig,0);B[i]=complex((a[i]&4095)*sig,0);
		sig=b[i]<0?-1:1;b[i]=sig*b[i];
		C[i]=complex((b[i]>>12)*sig,0);D[i]=complex((b[i]&4095)*sig,0);
	}fft(A);fft(B);fft(C);fft(D);
	for(int i=0;i<N;++i){
		complex t=B[i];
		B[i]=(A[i]*D[i])+(B[i]*C[i]);
		A[i]=A[i]*C[i];C[i]=t*D[i];
	}ifft(A);ifft(B);ifft(C);
	for(int i=0;i<N;++i){
		ll ca=round(A[i].a),cb=round(B[i].a),cc=round(C[i].a);
		c[i]=ca*(1<<24)+cb*4096+cc;
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%d",a+i);
	int n1=0,n2=0;
	for(int i=1;i<=n;++i)if(a[i]<0)b[++n2]=a[i];else a[++n1]=a[i];
	sort(a+1,a+n1+1);sort(b+1,b+n2+1);
	if(!n2){
		for(int i=1;i<=n1;++i)b[n1+1-i]=-a[i];
		n2=n1;n1=0;
	}
	for(int i=0;i<N;++i){
		db ang=2.0*M_PI/N*i;
		rev[i]=(rev[i>>1]>>1)+(i&1)*(N>>1);
		w[i]=complex(cos(ang),sin(ang));
	}
	for(int i=1;i<=n2;++i)D[i]=E[i]=B[i]=b[i];mult(D,E,F);
	for(int i=1;i<=n1;++i)A[i]=a[i];mult(A,B,C);
	for(int i=n1;i;--i)sm1[i]=sm1[i+1]+1ll*a[i]*b[1];
	for(int i=1;i<=n2;++i)sm2[i]=sm2[i-1]+1ll*b[i]*b[n2];
	if(n2==1){printf("%lld\n",sm1[1]);return 0;}
	ll mn=sm2[n2-1];int p=2;
	for(int i=4;i<=n2;i+=2){
		ll v=sm2[n2-i]+F[n2+n2-i+1]/2;
		if(v<mn)mn=v,p=i;
	}
	if(n1==0){printf("%lld",mn);return 0;}
	ll ans=8000000000000000000;
	for(int i=max(0,n2-n1);i<=n2;++i){
		ll tmp;
		if(i==1)tmp=1ll*b[n2]*b[n2-1];else
		if(i<p){
			if(i&1)tmp=F[n2+n2-i+2]/2+1ll*b[n2-i+1]*b[n2];else tmp=F[n2+n2-i+1]/2;
		}else tmp=mn-sm2[n2-i];
		ll v=tmp+sm1[n2-i+1]+C[n2-i+1];
		if(v<ans)ans=v;
	}printf("%lld",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值