Diorvh

导航

【题解】Codeforces Round #600(Div.2)

Codeforces Round #600(Div.2)

https://codeforces.com/contest/1253/problem

A.Single Push

思路:数组各位相减,得到b-a之后的。如果全为0,或者只有一段非0且数字相同则可行,否则不可行。具体实现的话,可以左右两边指针向中间搜到第一个不为0的数,再判断中间是否均为同一个数。复杂度\(O(n)\)

注意:多组数据一定要判断是否需要清空。这里我a[n+1]没有清0,结果WA on test55……

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+20;
int a[M];
int main(){
	int T;
	scanf("%d",&T);
	for (int z=1;z<=T;++z){
		int n;
		scanf("%d",&n);
		for (int i=1;i<=n;++i)
			scanf("%d",&a[i]);
		a[n+1]=0;
		int c;
		for (int i=1;i<=n;++i){
			scanf("%d",&c);
			a[i]-=c;
		}
		bool v=true;
		int l=1,r=n;
		while(l<=n&&a[l]==0)
			++l;
		while(r>l&&a[r]==0)
			--r;
		int std=a[l];
		if (std>0)
			v=false;
		else
			for (int i=l;i<=r;++i)
				if (a[i]!=std){
					v=false;
					break;
				}
		if (v)
			printf("YES\n");
		else
			printf("NO\n");
	}
	return 0;
} 

B.Silly Mistake

思路:可以用unordered_set记录目前在office里面的人,用unordered_map来记录一个人是否进入过office。对于每个event,如果为正,则判断一下是否之前已经进入过,如果已经进入过则非法,否则就记录这个人为1,并加入set;如果为负,则先判断一下是否在set里,如果没有则非法,否则就删掉这个人,这里再判断一下office是否空了,如果空了就直接当做一天结束(因为题目说天可以随便划分)。复杂度\(O(n)\)

注意:

  1. \(O(1)\)清空STL:直接建一个相同的,每次直接st=st0;
    UPD:不好意思我假了,直接这样赋值清空比clear还慢,最好的做法是swap(t,t0),这样是O(1)的。不过这样也只能使用一次,所以一般来说就clear就行了。这是多组数据的最优方案。
  2. 很多情况可以用unordered_set和unordered_map来优化复杂度(去掉log),不过小心被卡掉(哈希)。不过可以想办法保证不被卡掉,大概意思是自己写一个随机化的哈希,详情见neal的博客(cf上)。

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int M=2e6+20,P=1e6;
vector<int> vec;
unordered_set<int> st;
unordered_map<int,int> mp,mp0;
int c[M];
int main(){
	int n;
	scanf("%d",&n);
	bool v=true;
	for (int i=1;i<=n;++i)
		scanf("%d",&c[i]);
	for (int i=1;i<=n;++i){
		if (c[i]>0){
			if (mp[c[i]+P]){
				v=false;
				break;
			}
			else
				st.insert(c[i]),mp[c[i]+P]=1;
		}
		else{
			if (st.count(-c[i])){
				st.erase(-c[i]);
				if (st.empty())
					vec.push_back(i),mp=mp0;
			}
			else{
				v=false;
				break;
			}
		}
	}
	if (!st.empty())
		v=false;
	if (!v)
		printf("-1\n");
	else{
		printf("%d\n%d",vec.size(),vec[0]);
		for(int i=1;i<vec.size();++i)
			printf(" %d",vec[i]-vec[i-1]);
	}
	return 0;
}

C.Sweets Eating

思路:首先贪心很容易想到,每次总是先吃\(a_i\)更大的糖,这样总penalty最少。但题目要求输出k=1-m的所有情况。举几个例子之后可以发现,只要从小到大排序,那么k每增加1,答案会加上排序后的\(a[k\%m],a[k\%m+m],a[k\%m+2m]……\),下标不大于k。那么只要先排个序,之后再预处理每个\(k\%m\)的前缀和,再扫一遍输出答案即可。复杂度\(O(nlogn)\)

注意:不开LL见祖宗。

AC代码:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int M=2e5+20;
int a[M];
vector<LL> mmd[M]; 
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]); 
	sort(a+1,a+n+1);
	for(int i=0;i<m;++i){
		int j=1;
		mmd[i].push_back(a[i]);
		while(j*m+i<=n)
			mmd[i].push_back(mmd[i][j-1]+a[j*m+i]),++j;
	}
	LL ans=a[1];
	printf("%lld",ans);
	for (int i=2;i<=n;++i){
		ans+=mmd[i%m][i/m];
		printf(" %lld",ans);
	}
	putchar('\n');
	return 0;
}

D.Harmonious Graph

思路:这道题可以发现,题目的描述等价于:如果l到r有一条路径,那么l,r之间所有点都必须是连通的。那么我们可以想到用并查集(dsu)来处理。

首先遍历每一条边进行合并,之后再扫一遍将每个集合找出来,记录每个集合的元素个数cnt,最小点序号mn和最大点序号mx。

如果mx-mn+1=cnt,那么说明这个集合中的元素为mn\(\sim\)mx,满足了题意条件。

如果mx-mn+1<cnt,那么说明有些点在其他的集合里,我们必须要将这两个集合连起来才可以把缺失的点放进来。那么连接两个集合只需要添加一条边,直接答案加1即可。

当然,连接完之后的集合变大了,可能仍然不满足mx-mn+1=cnt,那么就继续上述步骤直到满足了该条件为止。当所有的集合都满足条件之后,就输出答案即可。

那么怎么来找到缺失的点所在的集合呢?我们对每个并查集只记录cnt,mn,mx,将所有并查集按照mn从小到大排序。若第i个集合不满足条件,那么肯定会缺失第i+1个集合的mn点,就将i和i+1集合合并,如果不满足就继续合并,直到满足了为止。这可以用优先队列来实现。

复杂度为并查集和优先队列的\(O(nlogn)\),实现常数有点大,不过140ms过掉了。

注意:好像有\(O(n+m)\)的做法……回头看Tutorial吧。

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int M=2e5+20;
int road[M];
int fnd(int x){
    return (x==road[x])?x:road[x]=fnd(road[x]);
}
void merge(int u,int v){
    road[fnd(u)]=fnd(v);
}
struct Bcj{
	int mn,mx,cnt;
	Bcj(int a=1e9+7,int b=0,int c=0):mn(a),mx(b),cnt(c){}
	void proc(int i){
		mn=min(mn,i);
		mx=max(mx,i);
		++cnt;
	}
	void clear(){
		mn=1e9+7;
		mx=cnt=0;
	}
	void merg(Bcj x){
		mn=min(mn,x.mn);
		mx=max(mx,x.mx);
		cnt+=x.cnt;
	}
}bcj[M];
struct cmp{
	bool operator()(Bcj x,Bcj y){
		return x.mn>y.mn;
	}
};
priority_queue<Bcj,vector<Bcj>,cmp> q;
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	int a,b;
	for (int i=1;i<=n;++i)
		road[i]=i;
	for (int i=1;i<=m;++i){
		scanf("%d%d",&a,&b);
		merge(a,b);
	}
	for(int i=1;i<=n;++i)
		bcj[i].mx=bcj[i].mn=i,bcj[i].cnt=1;
	for(int i=1;i<=n;++i)
		if (i!=fnd(i)&&bcj[i].mx)
			bcj[fnd(i)].proc(i),bcj[i].clear();
	//for(int i=1;i<=n;++i)
	//	cout<<bcj[i].mn<<' '<<bcj[i].mx<<bcj[i].cnt<<endl;
	for(int i=1;i<=n;++i)
		if (bcj[i].mx)
			/*cout<<bcj[i].mn<<' '<<bcj[i].mx<<' '<<bcj[i].cnt<<endl,*/q.push(bcj[i]);
	Bcj prs;
	int ans=0;
	while(!q.empty()){
		Bcj prs=q.top();
		q.pop();
		while(prs.cnt!=prs.mx-prs.mn+1){
			prs.merg(q.top());
			q.pop();
			++ans;
		}
	}
	printf("%d\n",ans);
	return 0;
}

E.Antenna Coverage

题意:一条街道m个点[1,m],有n个发射站,每个发射站有两个参数\(x_i\)\(s_i\),表示发射站位于\(x_i\),能覆盖\([x_i-s_i,s_i+s_i]\)。我们可以花1coin将每个发射站的\(s_i\)增加1,问最少花费多少coins能够完全覆盖[1,m]。保证两个发射站不会位于同一个地点。\(1\leq n\leq 80\)\(n\leq m\leq 100000\)

思路:VP的时候看数据范围猜到了应该是dp,但没想到怎么转移。实际上应该不算很难想。设dp[i]表示覆盖[1,i]的最小花费,那么转移方式有两种:

  1. 从[1,i-1]的方案直接转移过来。如果i处已经被覆盖了,那么dp[i]=dp[i-1];如果i处没有被覆盖,那么就让覆盖了i-1的那个基站再扩大1,有dp[i]=dp[i-1]+1。
  2. 从右端点<=i的基站转移过来。有dp[i]=min(dp[i],i-ant[j].r+dp[max(2*ant[j].x-i-1,0)])。其中ant[j]表示第j个基站,r表示基站的\(x_j+s_j\)。这一步要枚举所有符合条件的基站,转移复杂度为\(O(n)\)

答案为dp[m],判断是否被覆盖需要预先对[1,m]的区间建立vis数组预处理,最坏复杂度是\(O(nm)\),dp复杂度也是\(O(mn)\),所以总复杂度是\(O(nm)\)

注意:

  1. 要增加一个(x=0,s=0)的基站。由于用这个基站去覆盖任何区间的方案都是最差的,因此不会影响答案(每次都取最小值)。
  2. 注意预处理和dp的时候不要越界,左侧最小是0。

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+20;
struct Ant{
	int x,s,l,r;
	Ant(int a=0,int b=0,int c=0,int d=0):x(a),s(b),l(c),r(d){}
	bool operator<(Ant x){
		return r<x.r;
	}
}ant[100];
int dp[M],vis[M];
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;++i){
		int x,s;
		scanf("%d%d",&x,&s);
		ant[i]=Ant(x,s,x-s,x+s);
		for(int j=max(0,x-s);j<=min(m,x+s);++j)
			vis[j]=1;
	}
	dp[0]=0;
	for (int i=1;i<=m;++i){
		if (vis[i])
			dp[i]=dp[i-1];
		else
			dp[i]=dp[i-1]+1;
		for (int j=1;j<=n;++j)
			if (ant[j].r<=i)
				dp[i]=min(dp[i],i-ant[j].r+dp[max(2*ant[j].x-i-1,0)]);
	}
	printf("%d\n",dp[m]);
	return 0;
}

总结

这次把D题做出来了,虽然ABC代码实现和大佬们差不多,但感觉前面写的老有问题,写的有点慢,而且ABC各WA一发,说明写代码的风格和习惯还是不太好,D题想的比较快,表扬自己。E题没想出来其实不太应该,不过当时也是不太想写了。明晚cf争取上1800。

posted on 2019-11-19 00:27  diorvh  阅读(211)  评论(0编辑  收藏  举报