Codeforces Round /#679 (Div. 2) C.Perform Easily(二分/集合)


题目地址

http://codeforces.com/contest/1435/problem/C

大致题意

有一个长度为6的数组a,代表每根弦的初始值。

有另外一个数组b,代表需要弹奏的每个音符。

每根琴弦的琴格从1开始。(在弹吉他里应该叫x品吧)

如果我要弹音符$b_i$,找到$x$满足$b_i=x+a_j $则可以按琴弦j的琴格$x$来弹出这个音。($j$弦$x$品)

问怎么弹所有音符可以让最大的$x$和最小的$x$之间的差值最少?

题目思路

方法1:二分

方法来自大佬的博客:https://www.cnblogs.com/acceptedzhs/p/13876630.html

先对ab两个数组进行排序并且去重(这里a的去重是必要的,并不仅仅是为了速度)

设a的长度为m,b的长度为n,我们枚举n和m,复杂度$O(n) $

即对音符和琴弦进行枚举,$minx = b_j - a_i $,设$minx$就是之后最小的琴格,比$minx $还小的就是不可行的了。

我们定义”最悲惨的音符”是那些“刚好可以使用琴弦a[k]却无法使用琴弦$a_{k-1} $中的音符”里最大的。

tj

显然这样的音符最多只有$m-1 $个。(之前去重就是因为这里,不然$a_k = a_{k-1} $就出错了。

如果$b_1 < minx + a_1 $,则不可能存在”能使用$a_1 $但无法使用$a_2 $的音符”,对于这种情况我们直接判定这个$minx$不合法,跳过。

琴格的最大位置一定在这些“最悲惨的音符”和最大的那个音符中产生。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
using namespace std;

template<class T>inline void read(T &x){x=0;char o,f=1;while(o=getchar(),o<48)if(o==45)f=-f;do x=(x<<3)+(x<<1)+(o^48);while(o=getchar(),o>47);x*=f;}
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define ull unsigned long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define mkp make_pair
#define ft first
#define sd second
#define log(x) (31-__builtin_clz(x))
#define INF 0x3f3f3f3f
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; }
//#define INF 0x7fffffff
const int MAXN = 1e5+5;
ll a[8];
ll b[MAXN];
int n;
void solve(){
	int m = 6;
	rep(i,1,m) cin>>a[i];
	sort(a+1,a+1+m);
	m = unique(a+1,a+1+m)-a-1;
	cin>>n;
	rep(i,1,n) cin>>b[i];
	sort(b+1,b+1+n);
	n = unique(b+1,b+1+n)-b-1;
	//读入两个数组,排序去重
	ll minx,maxx,minn = 1ll<<62;
	rep(i,1,n){
		rep(j,1,m){
			maxx = 0;
			minx = b[i]-a[j];
			if(b[1]-a[1]<minx) continue;
			rep(k,1,m){
				//注意琴格从1开始
				//b[?]-a[k]>=minx 变形 b[?]>=minx+a[k]
				int px = lower_bound(b+1,b+1+n,minx+a[k])-b-1;
				//找到最后一个可以用a[k-1]但一定用不了a[k]的
				maxx = max(b[px]-a[k-1],maxx);
				
			}
			repb(k,m,1){
				if(b[n]-a[k]>=minx){//和音符中的最大值进行比较,并且找一根尽可能大的琴弦
					maxx = max(maxx,b[n]-a[k]);
					break;
				}
			}
			minn = min(maxx-minx,minn);
		}
	}
	cout<<minn<<endl;
}

int main(){
	solve();
}

方法2:集合

来自另一个大佬的博客:https://blog.csdn.net/I_have_a_world/article/details/109287448

我更喜欢这第二种方法,复杂度上应该都是$O(nlogn)$的。但是这个方法真的挺简洁易懂的。

两个数组可以不去重,但是去重的话效率应该会更高。

搞一个in[MAXN]数组,记录当前这个音符所在的弦。

设排序去重后有n个音符,m个琴弦。

一开始每个音符都在弦m上,用pair<ll,ll>来记录,分别记录$b_i - a_{in[i]} $和编号$i$。

把这些二元组都存在集合set里,然后每次取出first最小的(即$b_i - a_{in[i]} $)最大的元素i,和集合中first最大的做差。在这些数值中找最小值。

这样以后,把最小的i对应的in[i]–,即让他变得更大再加入集合。

重复这些步骤直到最小的元素也是用的1弦。为什么?因为这时候其他弦如果再往下换弦,也只能使$b_i - a_{in[i]} $变得更大,使最大-最小的值变大或者不变,所以到这里就没必要继续往下了。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<set>
#include<cmath>
using namespace std;

int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define mkp make_pair
#define ft first
#define sd second
typedef pair<ll,ll> pll;
const int MAXN = 1e5+5;
ll a[8];
ll b[MAXN];
int in[MAXN];
int n;
set<pll>st;
void solve(){
	int m = 6;
	rep(i,1,m) cin>>a[i];
	sort(a+1,a+1+m);
	m = unique(a+1,a+1+m)-a-1;
	cin>>n;
	rep(i,1,n) cin>>b[i];
    sort(b+1,b+1+n);
	n = unique(b+1,b+1+n)-b-1;
	//这里其实没太大必要去重,但是我想了一下还是去重了
    rep(i,1,n){
        in[i] = m;
        st.insert(mkp(b[i]-a[m],(ll)i));//一开始都是用m弦弹的
    }
    ll minn = st.rbegin()->ft-st.begin()->ft;
    while(1){
        pll now = *st.begin();
        st.erase(st.begin());//这边的参数是迭代器,这样删复杂度更小
        if(in[now.sd]==1) break;
        //如果最小的都是1,那么其他的音符换弦也不可能让结果更小了,因为往下换只会让bi-aj更大
        in[now.sd]--;//每次换成更小的弦
        st.insert(mkp(b[now.sd]-a[in[now.sd]],now.sd));
        minn = min(minn,st.rbegin()->ft-st.begin()->ft);
    }
    cout<<minn<<endl;
}
int main(){
    solve();
}

文章作者: REXWind
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 REXWind !
评论
  目录