zoukankan      html  css  js  c++  java
  • 题解 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;
    }
    
  • 相关阅读:
    【转】Testing, Thought, and Observations
    【转】Yet another software testing pyramid
    【转】Automated Testing and the Test Pyramid
    Environment setting up troubleshoot.
    [转] Spring相关
    流水账
    Abbreviation
    chrome中文本框样式问题
    Intellij IDEA 使用总结
    限制
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/12852376.html
Copyright © 2011-2022 走看看