zoukankan      html  css  js  c++  java
  • 【题解】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
    ");
    		else
    			printf("NO
    ");
    	}
    	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
    ");
    	else{
    		printf("%d
    %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('
    ');
    	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
    ",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]。保证两个发射站不会位于同一个地点。(1leq nleq 80)(nleq mleq 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
    ",dp[m]);
    	return 0;
    }
    
    

    总结

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

  • 相关阅读:
    【生活没有希望】poj1273网络流大水题
    SPOJ FASTFLOW网络流水题
    【生活没有希望】hdu1166敌兵布阵 线段树
    【生活没有希望】NOIP2010初赛 烽火传递 smartoj1475
    【填坑向】bzoj2038小Z的袜子 莫队
    (RMQ版)LCA注意要点
    【填坑向】spoj COT/bzoj2588 Count on a tree
    bzoj4364: [IOI2014]wall砖墙
    【听说是线段树】bzoj1012 [JSOI2008]最大数maxnumber
    bzoj4196 [Noi2015]软件包管理器 树链剖分+线段树
  • 原文地址:https://www.cnblogs.com/diorvh/p/11886557.html
Copyright © 2011-2022 走看看