题意:
就是给你一个数组,任意两点有一条边,权值为min(va[i],va[i+1],…,va[j])。现在你最多可以操作k次,选择一个数让它成为任意正整数x。现在问你这个图的最大的最短路是多少。
思考:
- 首先想到对于a到b的距离,要么是[a,b]这一段区间的最小值,要么是整个图中最小值的2倍。但是怎么求最大的那个呢?我想到,不管怎么样肯定是某两个点之间的距离,然后整个图的最小值你是避免不了的,那么我怎么能让这个距离最大呢?那么肯定就选相邻的两个点,这样才能更少的碰到最小值。因为是取[a,b]这一段区间的最小值嘛,你区间越大越容易变小。
- 所以我就枚举相邻两个点为a b,然后枚举之后,我的k次操作怎么操作呢?全部给数组中的最小值?但是我枚举的这两个可能又成为小的了。所以就直接枚举4中情况,让a变大,b不变,剩下的最小值变大。让b变大,a不变,剩下的最小值变大。让a不变b不变,剩下的最小值变大。让a变大b变大,剩下的最小值变大。然后取一个最大值就可以了。
- 但是到这里还有一个问题,我把a b拿出来了,剩下的最小值怎么取出来?如果每次都把剩下的拿出来,这样就是n×n的复杂度了。然后我就想,把a和b拿出来后,我只要剩下的第last个,也就是第几个最小值。印象中主席树是求第几个最小值,但是这题肯定也不用。然后我就想可以先排序,然后第几个最小值就是第几个。但是还要记得判断拿出来的a和b的下标,是否在我拿的第last个前面,如果在的话,last要++。处理的时候也要先判断下标较小的那个,这样才是正确的。
- 赛后我看了下可以二分,其实二分更好写,思维本质也一样。比如二分到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;
}
总结:
多多思考。