题解 CF1348 D,E,F Codeforces Round #638 (Div. 2)

比赛链接

CF1348D Phoenix and Science

分析问题的性质。首先,每块的大小是无关紧要的,重要的是你每天会切多少块。这取决于两个因素:当前已有的块数(d),当前所有块的总和(w)。假设你今天要新切(x)刀,那么显然,(i)必须满足:(0leq xleq d)。切完后,块数会变为(d+x)。在今天夜里过后,重量会变为(w+d+x)。那么显然,我们要求(w+d+xleq n)。也就是说,(0leq xleq min(d,n-w-d))。这就是我们一天中能做的事。

为了使天数最少,在最开始的阶段,我们肯定希望(w)快速增长。因此我们每天都令(x=min(d,n-w-d))。直到某一天,(n-w-d<0)时,这时候无法再进行任何的操作了。如果此时刚好(w=n),那就是圆满的结果。但是在更多的情况下,此时的(w)可能会比(n)差一点点。我们要想办法补上这“一点点”。

从后往前,枚举之前的每一次操作。设相邻两次操作后的块数分别为(d_1), (d_2),那么我们可以在(d_1), (d_2)之间插入一次操作,要满足在这次操作之后的块数(D)(d_1leq Dleq d_2)。同时,(D)要尽可能大,所以令(D=min(d_2,n-w))即可(若(min(d_2,n-w)<d_1),说明在(d_1), (d_2)之间插入不可行,我们直接从后往前枚举更小的操作即可)。如此从后往前,能插就插,显然,这样的贪心能够使总操作次数最少。且最坏的情况下,也一定能凑到(n),因为最前面可以做无限次(+1)的操作。

实测,当(n=10^9)时,只需要(29)次操作。故时间复杂度不超过(O(29n))

参考代码:

//problem:CF1348D
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

int main() {
	int T;cin>>T;while(T--){
		ll n;cin>>n;
		ll d=1;
		ll w=1;
		vector<pair<ll,ll> > ans;
		while(true){
			ll i=min(d+d,n-w);
			if(i>=d){
				w+=i;
				ans.pb(mk(i-d,i));
				d=i;
			}
			else break;
		}
		for(int i=SZ(ans)-2;i>=-1;--i){
			for(ll j=min(n-w,ans[i+1].se);j>=(i==-1?1:ans[i].se);--j,j=min(j,n-w)){
				w+=j;
				ans[i+1]=mk(ans[i+1].se-j,ans[i+1].se);
				ans.insert(ans.begin()+i+1,mk(j-(i==-1?1:ans[i].se),j));
				++i;++j;
				if(w==n)break;
			}
			if(w==n)break;
		}
		cout<<SZ(ans)<<endl;
		for(int i=0;i<SZ(ans);++i){
			cout<<ans[i].fi<<" ";
		}
		cout<<endl;
	}
	return 0;
}

CF1348E Phoenix and Berries

我们称两种放到篮子里的方式分别为:“同一棵树”,“同一品种”。

我们发现,一定存在一种最优方案,使得每棵树上的红莓或蓝莓作为“同一品种”被放入篮子的数量均不超过(k-1)个。因为如果一棵树上红莓或蓝莓存在大于等于(k)个“同一品种”,则完全可以把它们这篮换成“同一棵树”。于是可以想到一种朴素的DP。设(dp[i][x][y])表示考虑了前(i)棵树,红莓“同一品种”的数量(mod k)(x)个,蓝莓“同一品种”的数量(mod k)(y)个,最多放满了多少个篮子。转移时枚举当前树上“同一品种”的红、蓝莓各有多少个,其他全部作为“同一棵树”。时间复杂度(O(nk^4))

考虑优化。一个想法是只枚举选多少红莓作为“同一品种”。剩下的,不管是作为“同一棵树”的莓,还是作为“同一品种”的蓝莓,都放在一起计算:用总数除以(k)下取整。我们设(dp[i][j]),存一个( exttt{bool})值,表示考虑了前(i)棵树,是否能从每棵树上取若干个“同一品种”的红莓,使得取走的红莓数量(mod k=j),且剩下的红、蓝莓可以完美配对。这里完美配对的意思是,如果剩下的莓子总数为(s),则能装满(lfloorfrac{s}{k} floor)个篮子。转移时,枚举从当前树上取走(x)个红莓作为“同一品种”((0leq xleq min(k-1,a_i)))。设取走的数量为(x),则可以从(dp[i-1][j]),转移到(dp[i][(j+x)mod k])。这样转移的前提是:要么(x=a_i)(即这棵树上不剩下任何红莓),要么((a_i-x)mod k+b_igeq k)(即,剩下的红莓,能和剩下的蓝莓凑一筐“同一棵树”,再把多出来的蓝莓丢进蓝莓堆里)。

这个DP的潜在前提是,一定存在一种最优方案,使得每棵树上作为“同一棵树”的筐不超过(1)。这与我们第一个(O(nk^4))暴力DP用到的结论是相对应的:是问题的两个极端,而贪心算法往往就要去考虑这种极端。

最后,枚举一个(j),若(dp[n][j]= exttt{true}),则可以用(lfloorfrac{sum_{i=1}^{n}(a_i+b_i)-j}{k} floor)更新答案。

时间复杂度(O(nk^2))

参考代码:

//problem:CF1348E
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=500;
int n,K,a[MAXN+5],b[MAXN+5];
bool dp[MAXN+5][MAXN+5];
int main() {
	cin>>n>>K;
	dp[0][0]=true;
	ll sum=0;
	for(int i=1;i<=n;++i){
		cin>>a[i]>>b[i];
		sum+=a[i]+b[i];
		for(int j=0;j<K;++j)if(dp[i-1][j]){
			for(int k=0;k<=a[i]&&k<K;++k){
				if(k!=a[i]%K&&(a[i]-k)%K+b[i]<K)continue;
				dp[i][(j+k)%K]=true;
			}
		}
	}
	for(int i=0;i<K;++i)if(dp[n][i]){
		cout<<(sum-i)/K<<endl;return 0;
	}
	return 114514;
}

CF1348F Phoenix and Memory

如果只找出一组可行解,这是经典的贪心问题。我们把每个位置可选的值域区间(也就是(n)条线段)拿出来,按左端点从小到大排序。在值域上做扫描线。假设当前扫描到(i)。维护一个:左端点小于等于(i),且还未被使用的线段的集合。然后从集合里选出右端点最小的。在这条线段所表示的位置上填上值(i)。把该线段从集合里删除。然后继续考虑下一个值(i+1)

这样就能求出一组解。

考虑构造一组解,我们可以在已知解中交换两个位置上的值

不妨枚举其中一个位置。设它上面填的值为(x),它可以填的值的范围为([l,r])。显然,如果要找一个值和当前值交换,则这个值只可能在([l,x-1])([x+1,r])中。且被交换的这个值,它所在位置的限制区间,必须包含(x)。具体来说,对于([l,x-1])的值,它所在位置的限制区间,右端点必须(geq x);对于([x+1,r])的值,它所在位置的限制区间,左端点必须(leq x)。于是问题转化为求序列区间最大/最小值。可以用st表或线段树维护。

时间复杂度(O(nlog n))

参考代码:

//problem:CF1348F
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=2e5;
int n,limL[MAXN+5],limR[MAXN+5],pos[MAXN+5],ans1[MAXN+5],ans2[MAXN+5];
struct Limits{
	int l,r,pos;
	bool operator<(const Limits& rhs)const{
		return l<rhs.l;
	}
}a[MAXN+5];
struct Rmq{
	bool flag_max1min0;
	int arr[MAXN+5],st[MAXN+5][21],_log2[MAXN+5];
	void build(int n){
		for(int i=1;i<=n;++i)st[i][0]=i;
		for(int j=1;j<=20;++j){
			for(int i=1;i+(1<<(j-1))<=n;++i){
				if(flag_max1min0){
					st[i][j]=(arr[st[i][j-1]]>=arr[st[i+(1<<(j-1))][j-1]]?st[i][j-1]:st[i+(1<<(j-1))][j-1]);
				}
				else{
					st[i][j]=(arr[st[i][j-1]]<=arr[st[i+(1<<(j-1))][j-1]]?st[i][j-1]:st[i+(1<<(j-1))][j-1]);
				}
			}
		}
		_log2[0]=-1;
		for(int i=1;i<=n;++i)_log2[i]=_log2[i>>1]+1;
	}
	pii query(int l,int r){
		int k=_log2[r-l+1];
		pii res;
		if(flag_max1min0){
			res.se=(arr[st[l][k]]>=arr[st[r-(1<<k)+1][k]]?st[l][k]:st[r-(1<<k)+1][k]);
		}
		else{
			res.se=(arr[st[l][k]]<=arr[st[r-(1<<k)+1][k]]?st[l][k]:st[r-(1<<k)+1][k]);
		}
		res.fi=arr[res.se];
		return res;
	}
}rmxq,rmnq;

int main() {
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>a[i].l>>a[i].r;
		limL[i]=a[i].l;limR[i]=a[i].r;
		a[i].pos=i;
	}
	sort(a+1,a+n+1);
	multiset<pii>ms;
	for(int i=1,j=1;i<=n;++i){
		while(j<=n&&a[j].l==i){
			ms.insert(mk(a[j].r,a[j].pos));
			++j;
		}
		assert(!ms.empty());
		pii p=*ms.begin();
		ms.erase(ms.begin());
		ans1[p.se]=i;
	}
	bool uniq=true;
	rmxq.flag_max1min0=1;
	rmnq.flag_max1min0=0;
	for(int i=1;i<=n;++i){
		assert(ans1[i]);
		assert(!pos[ans1[i]]);
		pos[ans1[i]]=i;
		rmxq.arr[ans1[i]]=limR[i];
		rmnq.arr[ans1[i]]=limL[i];
	}
	rmxq.build(n);
	rmnq.build(n);
	for(int i=1;i<=n;++i){
		int l=limL[pos[i]];
		int r=limR[pos[i]];
		assert(i>=l&&i<=r);
		if(l<i){
			pii re=rmxq.query(l,i-1);
			if(re.fi>=i){
				uniq=false;
				for(int j=1;j<=n;++j)ans2[j]=ans1[j];
				swap(ans2[pos[i]],ans2[pos[re.se]]);
				break;
			}
		}
		if(r>i){
			pii re=rmnq.query(i+1,r);
			if(re.fi<=i){
				uniq=false;
				for(int j=1;j<=n;++j)ans2[j]=ans1[j];
				swap(ans2[pos[i]],ans2[pos[re.se]]);
				break;
			}
		}
	}
	if(uniq){
		cout<<"YES"<<endl;
		for(int i=1;i<=n;++i)cout<<ans1[i]<<" 
"[i==n];
	}
	else{
		cout<<"NO"<<endl;
		for(int i=1;i<=n;++i)cout<<ans1[i]<<" 
"[i==n];
		for(int i=1;i<=n;++i)cout<<ans2[i]<<" 
"[i==n];
	}
	return 0;
}
原文地址:https://www.cnblogs.com/dysyn1314/p/12852376.html