CF 963 D. Med-imize

原题链接:Problem - D - Codeforces

题意:给一个长度为n的数组,每次从数组中删去一段长度为k的连续子数组,如果数组长度小于等于k就停止删除,问最终数组的中位数最大是多少?

思路:二分+dp。从题目中可以的到的信息是,最终的数组长度一定小于等于k。那么很容易可以想到二种特殊情况,第一种是k=1的时候,中位数是数组里面最大的数,第二种是如果一次也不要删,那么可以直接取出中位数。

对于剩下的普遍情况,可以能够明显想到的是在数组里面肯定有些数是不可能加入最终数组的。那么什么数有可能成为最终的数组?

如果n可以被k整除那么,最终的数组长度就是k,如果不能整除,那么最终的数组长度就是n%k。那么这最终数组里面可以是什么数呢?首先数组下标从0开始,举个例子,如果n=8,k=3,那么数组的下标是:0,1,2,3,4,5,6,7,将这些下标全部对k取余,下标就是:0,1,2,0,1,2,0,1。可以发现随便删除3个连续的数字,留下来的一定是0,1,2这个循环节,并且8%3=2,所以留下来的下标一定是0,1。可以发现这个规律普遍存在全部的情况,那么可以抽象一下,最终数组的长度是v,那么能够构成这个数组的元素在原数组中,下标对k取余之后的值是,0,1,2,3,......,v-1。

对于最终的数组,一定要排序才可以知道中位数是多少吗?其实不用的。如果中位数是x,使用一个数来记录一下,那么大于等于x那么就+1,否则-1,如果最终的数大于0就代表x可以是中位数。注意这只是说大于等于x可以是中位数,但不是指x是中位数,例如:1 2 2 6 6 6 6 6 8 9 9,大于等于2的数有很多。看例子可以发现满足二分性质,所以可以二分中位数来求解这个问题。

总结一下,如果k=4,n=7,数组下标就是:0,1,2,3,0,1,2。最终数组的长度就是3,最终待选的下标是:0,1,2,0,1,2 。例如删前面的4个数就是后面的0,1,2,删中间的就是前面的0,后面的1,2。如果将这些数堆起来看呢?

0,1,2

0,1,2

那么就可以抽象成从0走到2,最终大于中位数的数是不是大于0就可以了,这样堆起来看就明显可以发现dp的转移方程了,具体见代码。

//冷静,冷静,冷静
//调不出来就重构
#pragma GCC optimize(2)
#pragma GCC optimize("O3")
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<ll,ll> pii;
const int N=1e6+10,mod=1000000007;
vector<vector<ll>> mp;
ll chang,kuan,p[N];
bool is(ll x)
{
	vector<vector<ll>> f(chang+10,vector<ll>(kuan+10,-1e9));
	for(int i=1;i<=chang;i++)
	{
		for(int j=1;j<=kuan;j++)
		{
			if(j==1)
			{
				if(mp[i][j]>=x)f[i][j]=1;
				else f[i][j]=-1;
				if(i>1)
				{
					f[i][j]=max(f[i-1][j],f[i][j]);
				}
				continue;
			}
			if(i==1)
			{
				f[i][j]=max(f[i][j],f[i][j-1]+(mp[i][j]>=x?1:-1));
				continue;
			}
			f[i][j]=max(f[i][j],f[i-1][j]);
			f[i][j]=max(f[i][j],f[i][j-1]+(mp[i][j]>=x?1:-1));
			f[i][j]=max(f[i][j],f[i-1][j-1]+(mp[i][j]>=x?1:-1));
		}
	}
	for(int i=1;i<=chang;i++)
	{
		if(f[i][kuan]>0)return 1;
	}
	return 0;
}
void jiuyuan()
{
	ll n,m;cin>>n>>m;
    //如果写上二种情况的特判,可以快700ms
	for(int i=1;i<=n;i++)
	{
		cin>>p[i];
	}
	chang=n/m+(n%m>0);kuan=(n%m==0?m:n%m);
//	cout<<n/m<<' '<<(n%m>0)<<endl;
	vector<vector<ll>> op(chang+10,vector<ll>(kuan+10));
	mp=op; 
//	cout<<chang<<' '<<kuan<<endl;
	for(int i=1;i<=chang;i++)
	{
		for(int j=1;j<=kuan;j++)
		{
			mp[i][j]=p[(i-1)*m+j];
		}
	}
	ll l=1,r=1e9;
	while(l+1<r)
	{
		ll mid=l+r>>1;
		if(is(mid))l=mid;
		else r=mid;
	}
	if(is(r))l=r;
	cout<<l<<endl;
}
int main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	ll T=1;
	cin>>T;
	while(T--)
	{
		jiuyuan();
	}
    return 0;
}

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值