Codeforces Round #679 (Div. 2) C.Perform Easily 有两种不同方法的题解

题目地址

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

大致题意

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

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

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

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

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

题目思路

方法1:二分

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

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

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

即对音符和琴弦进行枚举, m i n x = b j − a i minx=b_j-a_i minx=bjai,设 m i n x minx minx就是之后最小的琴格,比 m i n x minx minx还小的就是不可行的了。

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

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

如果 b 1 < m i n x + a 1 b_1<minx+a_1 b1<minx+a1,则不可能存在"能使用 a 1 a_1 a1但无法使用 a 2 a_2 a2的音符",对于这种情况我们直接判定这个 m i n x minx 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 ( n l o g n ) O(nlogn) O(nlogn)的。但是这个方法真的挺简洁易懂的。

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

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

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

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

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

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

重复这些步骤直到最小的元素也是用的1弦。为什么?因为这时候其他弦如果再往下换弦,也只能使 b i − a i n [ i ] b_i-a_{in[i]} biain[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.xyz

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值