牛客多校-Link with Arithmetic Progression-(三分总结)

J

题意:
就是给你一个数组,然后你每次可以让一个数a变为另一个数b,花费为(a-b)×(a-b)。然后问你让这个数组变成等差序列的最小花费。

  1. 既然要变成等差序列,我也不知道变成什么样的,而且我不知道首相应该是多少,公差应该是多少。但是不同的公差花费肯定会变化,感觉应该是一个二次函数,那么我就可以三分公差。三分之后怎么判断哪一个公差更好呢?
  2. 到这里,既然我知道公差了,那么我对原数组,每个地方都减去d*(i-1)。也就是让这个数组全部变成首相。但是我现在还不知道首相是多少,那么我就可以贪心选择首相,让这些数变成首相的花费最小。
  3. 那么就明显了,可以求出来总和/n,那么首相成为这个是最优的,因为花费就是(a-首相)*(a-首相),那么总和最小就是让首相成为平均数。因为这个是平方,所以要让每个数和首相的差尽量小。如果不是平方,那么就是求中位数,因为我要让总体最小,而不考虑每个数和首相的差。 当然可以求出来(vb[i]-x)×(vb[i]-x)+(vb[i+1]-x) ×(vb[i+1]-x)+…,展开后是一个关于x的二次函数,那么也可以直接求出来极值点。
  4. 当然可以先三分公差,再三分首相,但是这样复杂度是n*log(n)*long(n),有时候就容易超时,不过这也是一种做法。
  5. 还有值得注意的地方是,有时候会卡double,要开long double,输出用printf(“%.12Lf”);还有eps的大小,有时候差一个就会超时,就从9 10左右取,如果超时就 8 7 6。

代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db long double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);

using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 2e5+10,M = 2010;

int T,n,m,k;
db va[N];
db vb[N];

db check(db mid)
{
	db sum = 0,ans = 0;
	for(int i=1;i<=n;i++)
	{
		vb[i] = va[i]-mid*(i-1);
		sum += vb[i];
	}
	sum /= n;
	for(int i=1;i<=n;i++) ans += (vb[i]-sum)*(vb[i]-sum);
	return ans;
}

void solve()
{
	db l = -1e9,r = 1e9;
	while(r-l>1e-10)
	{
		db mid = (r-l)/3.0;
		db mid1 = l+mid,mid2 = r-mid;
		if(check(mid1)<check(mid2)) r = mid2;
		else l = mid1;
	}
	printf("%.12Lf\n",check(l));
}

signed main()
{
	IOS;
	cin>>T;
	while(T--)
	{
		cin>>n;
		for(int i=1;i<=n;i++) cin>>va[i];
		solve();
	}
	return 0;
}

2021济南-D Arithmetic Sequence

题意:
就是给你一个数组,每次你可以让一个数加1或者减1,然后把这个数组变成等差序列的最小花费。

思考:

  1. 这一题和上面的差不多,公差首相都不知道,那么就可以三分公差。同理,既然我知道公差了,那么我对原数组,每个地方都减去d*(i-1)。也就是让这个数组全部变成首相。但是我现在还不知道首相是多少,那么我就可以贪心选择首相,让这些数变成首相的花费最小。
  2. 这个题目花费就是你的操作次数,这次怎么确定首相是多少呢?这里其实就是经典的仓货选址问题。那么就选在这些数的中位数上,这样总和的差值就是最小的。注意上面的题目也提到了,不同的价值花费,你的首相选法是不一样的。
  3. 现在呢,怎么求中位数呢?sort排序?但是这样复杂度达到log(公差范围)×n×log(n)这直接超时了。但是还必须要求中位数,那么就可以用c++自带的求一个数组第k小的函数,底层是快排比较快。还有就是需要开__int128,中位数可能很大。

代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);

using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 2e5+10,M = 2010;

int T,n,m,k;
int va[N];
__int128 vb[N];

void print(__int128 x)
{
    if(x<0){putchar('-');x = -x;}
    if (x/10) print(x / 10);
    putchar(x % 10 + '0');
}

__int128 check(__int128 mid)
{
	for(int i=1;i<=n;i++) vb[i] = va[i]-mid*(i-1);
	nth_element(vb+1,vb+(n+1)/2,vb+n+1);//求第(n+1)/2小的数
	__int128 sum = vb[(n+1)/2],ans = 0;//排序后,这个数就在(n+1)/2这里
	for(int i=1;i<=n;i++)
	{
		if(vb[i]<sum) ans += sum-vb[i];
		else ans += vb[i]-sum;
	}
	return ans;
}

void solve() //整数三分
{
	int l = -1e13,r = 1e13;
	while(l<r)
	{
		int mid = (r-l)/3;
		int mid1 = l+mid,mid2 = r-mid;
		if(check(mid1)<=check(mid2)) r = mid2-1;
		else l = mid1+1;
	}
	print(check(l));
}

signed main()
{
	IOS;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>va[i];
	solve();
	return 0;
}

2020南京-F-Fireworks

题意:
制作一个烟花需要n秒,然后你燃放制作的所有烟花需要m秒。无论此时有多少制作好的烟花,都需要m秒点燃。然后每个烟花成功燃放的概率是p/10000。然后小A会制作烟花,并释放,如果成功燃放那么小A就会开心那么就停止了。现在问你小A开心最小的期望时间。

思考:

  1. 以前做题期望就是一个固定值,到这里怎么会可以求最小的呢?因为可以发现,这个期望并不是求总的。而是求某个时间的期望,这个期望是所有时间期望的最小值。
  2. 那么这样就好办了呀,只要我知道时间,和这个时间发生的概率,那么这个时间的期望就求出来了。但是难道要枚举时间?也就是枚举做多少烟花?这样太多了,枚举不了。可以先设做x个烟花,那么时间就是n*x+m,成功燃放的概率就是P = 1-全都不燃放的概率 = 1-(1-p)x。那么期望就可以直接求出了。
  3. 但是这样怎么求最小值呢?对于期望E(x) = (n*x+m)/P。为什么是除以成功的概率呢?因为根据伯努利实验的期望公式,此时期望就是1/P。打表或者求导可以发现,这是一个单峰凹凸函数类型的,所以可以三分求峰值。

代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
		
using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 2e5+10,M = 2010;

int T;
db n,m,p;

db check(int x)
{
	db sum = n*x+m,res = 1-pow((1-p*0.0001),x);
	db ans = sum/res;
	return ans;
}

void solve()
{
	int l = 0,r = 1e9;
	while(l<r)
	{
		int mid = (r-l)/3;
		int mid1 = l+mid,mid2 = r-mid;
		if(check(mid1)<=check(mid2)) r = mid2-1;
		else l = mid1+1;
	}
	printf("%.12lf\n",check(l));
}

signed main()
{
	IOS;
	cin>>T;
	while(T--)
	{
		cin>>n>>m>>p;
		solve();
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值