题目
现在 Dmitry 有一个会话,他必须通过 n 次考试。会议从第 1 天开始,持续 d 天。第i次考试将在ai(1≤ai≤d)那天进行,所有ai-都是不同的。
样本,其中 n=3,d=12,a=[3,5,9]。橙色——考试日。第一次考试前,Dmitry 休息 2 天,第二次考试前休息 1 天,第三次考试前休息 3 天。
对于课程安排,Dmitry 考虑了一个特殊值 μ——所有考试的考试前休息时间中的最小值。例如,对于上图,μ=1。换句话说,对于日程安排,他精确地计算了 n 个数字——他在考试 i-1 和考试 i 之间休息了多少天(对于 i=0,在课程开始和考试 i 之间)。然后它找到 μ——这 n 个数中的最小值。
德米特里相信他可以改进会议的日程安排。他可能会要求更改一次考试的日期(更改 ai 的任意值)。帮助他更改日期,使所有ai保持不同,并且μ的值尽可能大。
例如,对于上面的时间表,Dmitry 将第二次考试移到课程的最后是最有利的。新的时间表将采用以下形式:
现在考试前的休息时间等于 [2,2,5]。所以,μ=2。
Dmitry 可以保持提议的时间表不变(如果没有办法移动一项考试以使情况有所改善)。
输入
输入数据的第一行包含一个整数 t (1≤t≤104)——输入测试用例的数量。测试用例的描述如下。
在每个案例之前的测试中都写了一个空行。
每个测试用例的第一行包含两个整数 n 和 d (2≤n≤2⋅105,1≤d≤109)——分别是考试次数和会话长度。
每个测试用例的第二行包含 n 个整数 ai (1≤ai≤d,ai<ai+1),其中第 i 个数字表示第 i 次考试的日期。
保证所有测试用例的 n 之和不超过 2⋅105。
输出
对于每个测试用例,如果 Dmitry 可以将任何一项考试转移到任意一天,则输出 μ 的最大可能值。 ai 的所有值都应保持不同。
题解思路
求最值,先考虑二分(毕竟没直接贪心出结论的本事)。
当u值过大的时候肯定转移不出合适的结果。
当过小的时候,一定存在一种方法让他转移出合适的结果。
这样我们就可以根据二分的u值来贪心出适合的转移方法,如果方法不存在就是u值过大。
怎么贪心?
只能转换一次数组。
我们先处理出能直接转移到的超级位置。
当发现这个数不符合u值,让他先转移到超级位置,并积累(只能转换一次,出现2次这样的数就是不符合答案了)
此时这个i值被转移了,为了不影响之后的计算,我们将它的位置往前面推。
如何此时没有超级位置能够转移,就考虑将自己往后面移动几位,或者将前面的数往前移动几位。
判断两个区间是否存在放的位置。
特判最后一个位置。(WA了2次)
因为最后一个数的往后移动,只需前面区间符合条件即可。
这题还有用multiset直接贪心的做法。
非常暴力
枚举每个可能需要移动的数,将所有区间放入 multiset , 这个数要么移动到最大的区间的中间,要么移动到最后面,直接处理与这个点有关的区间,将前后两点连接,再判断放哪里,暴力更新答案即可。
特判第n个点即可。
AC代码
二分解法
#include <bits/stdc++.h>
//#include <unordered_map>
//priority_queue
#define PII pair<int,int>
#define ll long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 200100;
int n , d ;
int a[N] ;
int b[N] ;
bool che(int p )
{
int falg = 0 ;
int cnt = 0 ;
for (int i = 0 ; i <= n + 1 ; i++ )
b[i] = a[i] ;
for (int i = 1 ; i <= n ; i++ )
{
int st = b[i] - b[i-1] - 1 ;
if ( st >= 2*p + 1 )
cnt++;
}
if ( d - b[n] - 1 >= p )
cnt++;
int fx = 0 ;
for (int i = 1 ; i <= n ; i++ )
{
int st = b[i] - b[i-1] - 1 ;
if ( st < p )
{
falg++;
if ( falg > 1 )
return false ;
if (cnt)
b[i] = b[i-1] ;
else
{
if ( i == n )
{
if ( d - b[i-1] - 1 >= p )
{
fx++;
continue ;
}
}
if ( b[i+1] - b[i-1] - 1 >= 2*p + 1 )
fx++;
else if ( i > 1 )
{
if ( b[i] - b[i-2] - 1 >= 2*p + 1 )
fx++;
else
return false ;
}else
return false ;
}
}
}
if (fx)
cnt++;
if ((cnt && falg == 1 )|| !falg )
return true ;
else
{
return false ;
}
}
void solve()
{
cin >> n >> d ;
a[n+1] = d ;
for (int i = 1 ; i <= n ; i++ )
cin >> a[i] ;
int t1 = 0 , t2 = d - 1 ;
while ( t1 < t2 )
{
int mid = t1 + t2 + 1 >> 1 ;
if (che(mid))
t1 = mid ;
else
t2 = mid - 1 ;
}
cout << t1 << "\n" ;
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T ;
cin >> T ;
while (T--)
solve() ;
return 0 ;
}
multiset 解法
#include <bits/stdc++.h>
//#include <unordered_map>
//priority_queue
#define PII pair<int,int>
#define ll long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 200100;
int n , d ;
int a[N] ;
void solve()
{
cin >> n >> d ;
multiset<int> s ;
for (int i = 1 ; i <= n ; i++ )
cin >> a[i] ;
a[0] = 0 ;
int fx = d - a[n] - 1 ;
for (int i = 1 ; i <= n ; i++ )
{
s.insert(a[i]-a[i-1]-1) ;
}
int ans = 0 ;
for (int i = 1 ; i < n ; i++ )
{
s.erase(s.find(a[i] - a[i-1] - 1 )) ;
s.erase(s.find(a[i+1] - a[i] - 1 )) ;
s.insert(a[i+1] - a[i-1] - 1 ) ;
int t1 = *s.begin() , t2 = *s.rbegin() ;
ans = max(ans,min(t1,max((t2-1)/2,fx))) ;
s.insert(a[i] - a[i-1] - 1 ) ;
s.insert(a[i+1] - a[i] - 1 ) ;
s.erase(s.find( a[i+1] - a[i-1] - 1)) ;
}
s.erase(s.find(a[n]-a[n-1]-1)) ;
fx = d - a[n-1] - 1 ;
int t1 = *s.begin() , t2 = *s.rbegin() ;
ans = max(ans,min(t1,max((t2-1)/2,fx))) ;
cout << ans << "\n" ;
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T ;
cin >> T ;
while (T--)
solve() ;
return 0 ;
}