bzoj3771 Triple

13 篇文章 0 订阅

Description


我们讲一个悲伤的故事。
从前有一个贫穷的樵夫在河边砍柴。
这时候河里出现了一个水神,夺过了他的斧头,说:
“这把斧头,是不是你的?”
樵夫一看:“是啊是啊!”
水神把斧头扔在一边,又拿起一个东西问:
“这把斧头,是不是你的?”
樵夫看不清楚,但又怕真的是自己的斧头,只好又答:“是啊是啊!”
水神又把手上的东西扔在一边,拿起第三个东西问:
“这把斧头,是不是你的?”
樵夫还是看不清楚,但是他觉得再这样下去他就没法砍柴了。
于是他又一次答:“是啊是啊!真的是!”
水神看着他,哈哈大笑道:
“你看看你现在的样子,真是丑陋!”
之后就消失了。
樵夫觉得很坑爹,他今天不仅没有砍到柴,还丢了一把斧头给那个水神。
于是他准备回家换一把斧头。
回家之后他才发现真正坑爹的事情才刚开始。
水神拿着的的确是他的斧头。
但是不一定是他拿出去的那把,还有可能是水神不知道怎么偷偷从他家里拿走的。
换句话说,水神可能拿走了他的一把,两把或者三把斧头。
樵夫觉得今天真是倒霉透了,但不管怎么样日子还得过。
他想统计他的损失。
樵夫的每一把斧头都有一个价值,不同斧头的价值不同。总损失就是丢掉的斧头价值和。
他想对于每个可能的总损失,计算有几种可能的方案。
注意:如果水神拿走了两把斧头a和b,(a,b)和(b,a)视为一种方案。拿走三把斧头时,(a,b,c),(b,c,a),(c,a,b),(c,b,a),(b,a,c),(a,c,b)视为一种方案。

所有数据满足:Ai<=40000

来自 https://www.lydsy.com/JudgeOnline/problem.php?id=3771

Solution


数据只给出了a的范围,这是在提示我们与n无关用桶做(滑稽
如果没有奇怪的限制可以直接写成卷积上,但是加上了限制就要考虑容斥了
考虑用a记录一把斧头拿一次的价值,b记录一把斧头拿两次的价值,c记录一把斧头拿三次的价值
答案就是a+(a*a-y*2)/2+(a*a*a-a*b*3-c*2)/6,这里的乘号是卷积

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <complex>

typedef std:: complex <double> com;
typedef double db;

const db pi=3.1415926535897932;
const int N=524289;

int rev[N];

com a[N],b[N],c[N],ans[N];

void FFT(com *a,int len,db f) {
    for (int i=0;i<len;i++) if (i<rev[i]) std:: swap(a[i],a[rev[i]]);
    for (int i=1;i<len;i*=2) {
        com wn(cos(pi/i),f*sin(pi/i));
        for (int j=0;j<len;j+=i*2) {
            com w(1,0);
            for (int k=0;k<i;k++) {
                com u=a[j+k],v=a[j+k+i]*w;
                a[j+k]=u+v; a[j+k+i]=u-v;
                w*=wn;
            }
        }
    }
    if (f==-1) for (int i=0;i<len;i++) a[i]/=len;
}

int main(void) {
    int n,max=0; scanf("%d",&n);
    for (int i=1;i<=n;i++) {
        int x; scanf("%d",&x);
        a[x]=1; b[x*2]=1; c[x*3]=1;
        max=std:: max(max,x*3);
    }
    int len,lg; for (len=1,lg=0;len<=max*2;len*=2,lg++);
    for (int i=0;i<len;i++) rev[i]=(rev[i/2]/2)|((i&1)<<(lg-1));
    FFT(a,len,1); FFT(b,len,1); FFT(c,len,1);
    for (int i=0;i<len;i++) ans[i]=a[i]+(a[i]*a[i]-b[i])/2.0+(a[i]*a[i]*a[i]-3.0*a[i]*b[i]+c[i]*2.0)/6.0;
    FFT(ans,len,-1);
    for (int i=0;i<len;i++) if ((int)(0.1+ans[i].real())>0) printf("%d %d\n", i,(int)(0.1+ans[i].real()));
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值