【2022 牛客第二场J题 Link with Arithmetic Progression】三分套三分/三分极值/线性方程拟合最小二乘法

博客介绍了如何利用三分法和最小二乘法解决一个数学问题:给定一个序列,目标是将其改造成等差数列,求最小的修改代价。首先通过三分法寻找最佳的等差值,然后用最小二乘法进行线性拟合,从而得到最优解。代码示例展示了具体的实现过程。
摘要由CSDN通过智能技术生成

题目链接

题面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题意:

给你一个长度为n的序列a,对于每一个元素,你可以更改其为任何值,但是代价为他们差值的平方,问你把这个序列改成等差数列的最小花费是多少

分析:

其实如果看这种最小值的题的话很容易想到的就是三分,因为三分他的函数曲线是呈二次函数型的,有一个极值,所以说可以用二次函数做,就是三分等差d,然后看左边三分之一处的d值代表的最小花费和右边三分之一处的最小花费来作比较,这个是三分的常用思路,然后在三分的判断函数里,是一个二次函数的曲线,只需要找到最值就行,但是这个精度卡的特别死,不能直接乘起来,要慢慢加起来,这也算是一种技巧把,那么看一下三分极值的代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
#define double long double
const int N=1e6+10;
int n;
double a[N],eps=1e-10;
double c[N];
namespace GTI
{
    char gc(void)
       {
        const int S = 1 << 16;
        static char buf[S], *s = buf, *t = buf;
        if (s == t) t = buf + fread(s = buf, 1, S, stdin);
        if (s == t) return EOF;
        return *s++;
    }
    int read(void)
       {
        int a = 0, b = 1, c = gc();
        for (; !isdigit(c); c = gc()) b ^= (c == '-');
        for (; isdigit(c); c = gc()) a = a * 10 + c - '0';
        return b ? a : -a;
    }
}
using GTI::read;
double cal(double d)
{
	double ans=0,b=0;
	for(int i=1;i<=n;i++)
	{
		b += a[i]-(i-1)*d;
	}
	b /= n;
	for(int i=1;i<=n;i++){
		double t = a[i]-(i-1)*d;
		ans += (t-b)*(t-b);
	}
	return ans;
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		n=read();
		for(int i=1;i<=n;i++) a[i]=read();
		double l=-1e9,r=1e9;
		while(fabs(r-l)>eps)
		{
			double lmid=l+(r-l)/3,rmid=r-(r-l)/3;
			if(cal(lmid)>cal(rmid)) l=lmid;
			else r=rmid;
		}
		printf("%.10Lf\n",cal(l));
	}
	return 0;
}


最小二乘法

等差数列可以看作是一条直线,然后相当于线性拟合,就把高中学过的最小二乘法给搬过来就行,但是注意要用那个乘法少的公式,那样损失的精度会小,下面请看代码:

 
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
#define double long double
const int N=1e6+10;
int n;
double a[N];
int read() {
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        n=read();
        double x=(n+1)/2.0;
        double y=0,t=0,tx=0;
        for(int i=1;i<=n;i++)
        {
            a[i]=read();
            y+=a[i];
            t+=i*a[i];
            tx+=i*i;
        }
        y/=n;
        double k=0;
        for(int i=1;i<=n;i++) k+=(i-x)*(a[i]-y);
        double ttt=0;
        for(int i=1;i<=n;i++) ttt+=(x-i)*(x-i);
        k/=ttt;
        double b=y-k*x;
        double ans=0;
        double tt=k+b;
        for(int i=1;i<=n;i++)
        {
            ans+=(a[i]-tt)*(a[i]-tt);
            tt+=k;
        }
        printf("%.10Lf\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇智波一打七~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值