数列删减题解

目录

本文提到过二分算法:不会的->戳我

题目描述

输入格式

输出格式

样例输入:

样例输出:

数据规模 :

第一部分:主要思路

第二部分:check()

第三部分:快速计算

注意事项:

第四部分:代码(代码中含注释)

第一种:

第二种:


本文提到过二分算法:不会的->戳我

题目描述

给定一个长度为 n 的数列 {an},和一个整数 k。每次操作,你可以在两种操作中选一种:

  • 选择某个数 ai,将它减一。即令 ai=ai-1。

  • 选择某两个数 ai 和 aj,将一个变为另一个。即令 ai=aj。

求最少多少次操作可以让数列的和小于等于 k。

输入格式

第一行一个整数 T,表示数据组数,

每组数据第一行两个整数 n 和 k,第二行 n 个整数a1到an。

输出格式

每组数据输出一行,代表答案。

样例输入:

4

1 10
20
2 69
6 9
7 8
1 2 1 3 1 2 1
10 1
1 2 3 1 2 6 1 6 8 10

样例输出:

10
0
2
7

数据规模 :

1 \le T \le 10^4,1\le n \le2\cdot10^5,1\le k\le10^{15},1\le a_i\le10^9,\sum n\le2\cdot10^5

第一部分:主要思路

这个题我们可以用二分来做,我们二分操作次数,如果check(mid)返回1,那就往小的二分,否则就说明当前操作次数太少了,往大的二分。


第二部分:check()

其实check的实现也很好想。check的复杂度一般是O(n),在这里,我们可以枚举替换操作次数,mid是总操作次数,那么mid-替换操作次数就是删减操作了,在这里删减操作是给最小的数操作更好,所以我们sort一下取a[1]就行了,剩下的就是快速计算操作后整个序列的总和了。


第三部分:快速计算

我们知道,前缀和可以在O(1)时间内算出a1~ai的总和,所以我们维护一个前缀和就可以了,

因为替换了i个,所以总和是i*(a[1]-(mid-i))+sum[n-i]-(mid-i);(其中sum是前缀和数组)


注意事项:

  1. 十年OI一场空,不开long long见祖宗。

  2. 还有要排序。

  3. 每一次都要memset一下前缀和数组,全改成0。

第四部分:代码(代码中含注释)

第一种:

#include<bits/stdc++.h>
using namespace std;
long long a[200010];//读入数组 
long long sum[200010];//前缀和数组 
bool check(long long mid,int n,long long k)//开long long,check函数 
{
	for(long long i=0;i<n&&i<=mid;i++)//枚举替换次数,最多替换min(n,mid)个 
	{
		long long res=mid-i;//删减操作次数 
		long long mi=a[1]-res;//删减后最小的数 
		long long ret=mi*(i)+sum[n-i]-res;//计算从a[1]到a[n]的总和 
		if(ret<=k)//总和小于k成立 
		{
			return 1;
		}
	}
	return 0;
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		int n;
		long long k;
		cin>>n>>k;
		memset(sum,0,sizeof(sum));//不要忘memset哦 
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];//读入 
		}
		sort(a+1,a+1+n);//sort排序 
		for(int i=1;i<=n;i++)
		{
			sum[i] = sum[i-1]+a[i];
		}
		long long l=0,r=LONG_LONG_MAX,ans=-1;//也可以改成r=k,我习惯写r=LONG_LONG_MAX 
		while(l<=r)//敲二分模板,有两种写法,这是第一种 
		{
			long long mid=(l+r)/2;//计算mid 
			if(check(mid,n,k))//判断 
			{
				r = mid-1;//缩小范围 
				ans=mid;//记录答案 
			}
			else
			{
				l=mid+1;//扩大范围 
			}
		}
		cout<<ans<<'\n';//输出 
	}
    return 0;
}

第二种:

#include<bits/stdc++.h>
using namespace std;
long long a[200010];//读入数组 
long long sum[200010];//前缀和数组 
bool check(long long mid,int n,long long k)//开long long,check函数 
{
	for(long long i=0;i<n&&i<=mid;i++)//枚举替换次数,最多替换min(n,mid)个 
	{
		long long res=mid-i;//删减操作次数 
		long long mi=a[1]-res;//删减后最小的数 
		long long ret=mi*(i)+sum[n-i]-res;//计算从a[1]到a[n]的总和 
		if(ret<=k)//总和小于k成立 
		{
			return 1;
		}
	}
	return 0;
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		int n;
		long long k;
		cin>>n>>k;
		memset(sum,0,sizeof(sum));//不要忘memset哦 
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];//读入 
		}
		sort(a+1,a+1+n);//sort排序 
		for(int i=1;i<=n;i++)
		{
			sum[i] = sum[i-1]+a[i];
		}
		long long l=0,r=LONG_LONG_MAX;//也可以改成r=k,我习惯写r=LONG_LONG_MAX 
		while(l<r)//敲二分模板,有两种写法,这是第二种 
		{
			long long mid=(l+r)/2;//计算mid 
			if(check(mid,n,k))//判断 
			{
				r = mid;//缩小范围和记录答案合为一体 
			}
			else
			{
				l=mid+1;//扩大范围 
			}
		}
		cout<<r<<'\n';//输出 
	}
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
06-01
这道题是一道典型的费用限制最短路题目,可以使用 Dijkstra 算法或者 SPFA 算法来解决。 具体思路如下: 1. 首先,我们需要读入输入据。输入据中包含了道路的量、起点和终点,以及每条道路的起点、终点、长度和限制费用。 2. 接着,我们需要使用邻接表或邻接矩阵来存储图的信息。对于每条道路,我们可以将其起点和终点作为一个有向边的起点和终点,长度作为边权,限制费用作为边权的上界。 3. 然后,我们可以使用 Dijkstra 算法或 SPFA 算法求解从起点到终点的最短路径。在这个过程中,我们需要记录到每个点的最小费用和最小长度,以及更新每条边的最小费用和最小长度。 4. 最后,我们输出从起点到终点的最短路径长度即可。 需要注意的是,在使用 Dijkstra 算法或 SPFA 算法时,需要对每个点的最小费用和最小长度进行松弛操作。具体来说,当我们从一个点 u 经过一条边 (u,v) 到达另一个点 v 时,如果新的费用和长度比原来的小,则需要更新到达 v 的最小费用和最小长度,并将 v 加入到优先队列(Dijkstra 算法)或队列(SPFA 算法)中。 此外,还需要注意处理边权为 0 或负的情况,以及处理无法到达终点的情况。 代码实现可以参考以下样例代码: ```c++ #include <cstdio> #include <cstring> #include <queue> #include <vector> using namespace std; const int MAXN = 1005, MAXM = 20005, INF = 0x3f3f3f3f; int n, m, s, t, cnt; int head[MAXN], dis[MAXN], vis[MAXN]; struct Edge { int v, w, c, nxt; } e[MAXM]; void addEdge(int u, int v, int w, int c) { e[++cnt].v = v, e[cnt].w = w, e[cnt].c = c, e[cnt].nxt = head[u], head[u] = cnt; } void dijkstra() { priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q; memset(dis, 0x3f, sizeof(dis)); memset(vis, 0, sizeof(vis)); dis[s] = 0; q.push(make_pair(0, s)); while (!q.empty()) { int u = q.top().second; q.pop(); if (vis[u]) continue; vis[u] = 1; for (int i = head[u]; i != -1; i = e[i].nxt) { int v = e[i].v, w = e[i].w, c = e[i].c; if (dis[u] + w < dis[v] && c >= dis[u] + w) { dis[v] = dis[u] + w; q.push(make_pair(dis[v], v)); } } } } int main() { memset(head, -1, sizeof(head)); scanf("%d %d %d %d", &n, &m, &s, &t); for (int i = 1; i <= m; i++) { int u, v, w, c; scanf("%d %d %d %d", &u, &v, &w, &c); addEdge(u, v, w, c); addEdge(v, u, w, c); } dijkstra(); if (dis[t] == INF) printf("-1\n"); else printf("%d\n", dis[t]); return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值