CFdiv2-Empty Graph-(贪心or二分)

D

题意:
就是给你一个数组,任意两点有一条边,权值为min(va[i],va[i+1],…,va[j])。现在你最多可以操作k次,选择一个数让它成为任意正整数x。现在问你这个图的最大的最短路是多少。

思考:

  1. 首先想到对于a到b的距离,要么是[a,b]这一段区间的最小值,要么是整个图中最小值的2倍。但是怎么求最大的那个呢?我想到,不管怎么样肯定是某两个点之间的距离,然后整个图的最小值你是避免不了的,那么我怎么能让这个距离最大呢?那么肯定就选相邻的两个点,这样才能更少的碰到最小值。因为是取[a,b]这一段区间的最小值嘛,你区间越大越容易变小。
  2. 所以我就枚举相邻两个点为a b,然后枚举之后,我的k次操作怎么操作呢?全部给数组中的最小值?但是我枚举的这两个可能又成为小的了。所以就直接枚举4中情况,让a变大,b不变,剩下的最小值变大。让b变大,a不变,剩下的最小值变大。让a不变b不变,剩下的最小值变大。让a变大b变大,剩下的最小值变大。然后取一个最大值就可以了。
  3. 但是到这里还有一个问题,我把a b拿出来了,剩下的最小值怎么取出来?如果每次都把剩下的拿出来,这样就是n×n的复杂度了。然后我就想,把a和b拿出来后,我只要剩下的第last个,也就是第几个最小值。印象中主席树是求第几个最小值,但是这题肯定也不用。然后我就想可以先排序,然后第几个最小值就是第几个。但是还要记得判断拿出来的a和b的下标,是否在我拿的第last个前面,如果在的话,last要++。处理的时候也要先判断下标较小的那个,这样才是正确的。
  4. 赛后我看了下可以二分,其实二分更好写,思维本质也一样。比如二分到mid为答案,那么整个数组中的最小值*2都必须>=mid。然后枚举所有的相邻点a b,是否有a>=mid&&b>=mid,或者a>=mid||b>=mid。那么总操作次数就是变最小值的,和让某两个相邻的都>=mid。看看次数是否<=k即可。

代码:

#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 = 1e9;
const int N = 2e5+10,M = 2010;

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

int get(int a,int b)
{
	int maxn = 0;
	int t1 = min(idx[a],idx[b]),t2 = max(idx[a],idx[b]);
	
	int last = m+1;
	if(last>=t1) last++;
	if(last>=t2) last++;
	int sum = min(va[a],va[b]);
	if(last<=n) sum = min(sum,2*vb[last].fi);
	maxn = max(maxn,sum);
	
	last = m;
	if(last>=t1) last++;
	if(last>=t2) last++;
	sum = min(inf,va[b]);
	if(last<=n) sum = min(sum,2*vb[last].fi);
	maxn = max(maxn,sum);
	
	last = m;
	if(last>=t1) last++;
	if(last>=t2) last++;
	sum = min(va[a],inf);
	if(last<=n) sum = min(sum,2*vb[last].fi);
	maxn = max(maxn,sum);
	
	last = m-2+1;
	if(last>=t1) last++;
	if(last>=t2) last++;
	sum = min(inf,inf);
	if(last<=n) sum = min(sum,2*vb[last].fi);
	maxn = max(maxn,sum);
	return maxn;
}

signed main()
{
	IOS;
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		for(int i=1;i<=n;i++)
		{
			cin>>va[i];
			vb[i] = {va[i],i};
		}
		if(m==0)
		{
			int maxn = 0,minn = va[n];
			for(int i=1;i<n;i++)
			{
				minn = min(minn,va[i]);
				maxn = max(maxn,min(va[i],va[i+1]));	
			}
			cout<<minn<<"\n";
			continue;
		}
		if(m>=n)
		{
			cout<<inf<<"\n";
			continue;
		}
		sort(vb+1,vb+1+n);
		for(int i=1;i<=n;i++) idx[vb[i].se] = i; //下标vb[i].se的排序后的下标在哪
		int maxn = 0;
		for(int i=1;i<n;i++) maxn = max(maxn,get(i,i+1));
		cout<<maxn<<"\n";
	}
	return 0;
}

二分:
#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 = 1e9;
const int N = 2e5+10,M = 2010;

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

bool check(int mid)
{
	int sum = 0;
	for(int i=1;i<=n;i++) vb[i] = va[i];
	for(int i=1;i<=n;i++)
	{
		if(vb[i]*2<mid)
		{
			vb[i] = 1e9;
			sum++;
		}
	}
	int t1 = 0,t2 = 0;
	for(int i=1;i<n;i++)
	{
		if(vb[i]>=mid&&vb[i+1]>=mid) t1 = 1;
		if(vb[i]>=mid||vb[i+1]>=mid) t2 = 1;
	}
	if(t1) return sum<=m;
	else if(t2) return sum+1<=m;
	else return sum+2<=m;
}

signed main()
{
	IOS;
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		for(int i=1;i<=n;i++) cin>>va[i];
		int l = 0,r = 1e9;
		while(l<r)
		{
			int mid = (l+r+1)>>1;
			if(check(mid)) l = mid;
			else r = mid-1;
		}
		cout<<l<<"\n";
	}
	return 0;
}

总结:
多多思考。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值