Codeforeces #710 div3题解报告

A. Strange Table

题意:给你一个矩阵(n*m)的。这个矩阵从上到下由1,2,3````n * m填满.
再给你一个数x。问你在另一个矩阵(也是n * m,不过是从水平填起),这个x的位置是什么数.
思路: 算出x的坐标。然后再按矩阵的性质代入求值就行.
代码:

#include
using namespace std;
int main(){
	int T;
	cin>>T;
	while(T--){
		long long n,m,x;
		cin>>n>>m>>x;long long xx,yy;
		if(x%n==0)  xx=n; else xx=x%n;
		if(x%n==0) yy=x/n; else yy=x/n+1;
		long long ans=(xx-1)*m+yy;
		cout<<ans<<endl;
	}
}

B. Partial Replacement

题意:给一个长度为n,且仅有 . 和 * 的字符串。满足下列两条件下,求最少用多少个"x"将“ * ”替换。
条件1: 开头第一个*与最后一个 * 必须替换。
条件2: 两个替换后的 x 之间的距离不超过k。
思路: 贪心即可。从第一个 * 开始 每次都去找一个最大距离的 * 然后替换,直到最后一个 .

#include
using namespace std;
int n,k;string s;
int main(){
	int T;
	cin>>T;
	while(T--){
		cin>>n>>k;
		cin>>s;
		int tot=0;
		int begin=0;int end=0;int first=0;
		for(int i=0;i<n;i++) {
			if(s[i]=='*') tot++,end=i;
			if(s[i]=='*'&&first==0) begin=i,first=1;
			  
		}
		if(tot==1) {
			cout<<1<<endl; continue;
		} 
		if(tot==2) {
			cout<<2<<endl; continue;
		}
		int cnt=0;
		s[begin]='x';s[end]='x';
		for(int i=begin;i<n;){
			int lb=min(n-1,i+k);bool flag=false;
			if(lb>=end) break;
			for(int j=lb;j>i;j--) {
				if(s[j]=='*') {
					cnt++; flag=true;
					i=j;
					break;
				}
			}
			
		}
		cout<<cnt+2<<endl; 
	}
}

C. Double-ended Strings

题意: 给两个字符串a,b. 有两种操作: 1. 对a去头或者去尾。2. 对b去头或者去尾. 求最少操作使得a,b相同.
思路1: 暴力枚举 直接枚举所有可能长度,然后再枚举i,j位置的字串,复杂度是O(min(n,m)nm)
思路2 : dp 实际上这是求一个最长公共连续子序列。与传统LCS不同的是,我们要求的LCS要是连续的.不难得转移方程.
dp[i+1][j+1]=dp[i][j]+1;(a[i]=a[j])
dp[i+1][j+1]=0(a[i]!=a[j])
代码: (dp解决)

#include
using namespace std;
int dp[55][55];
int main(){
	int T;cin>>T;
	while(T--){
		string a,b;cin>>a>>b;
	int n=a.size();int m=b.size();
	int ans=0;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			if(a[i]==b[j]){
				dp[i+1][j+1]=dp[i][j]+1;
			}
			else dp[i+1][j+1]=0;
			ans=max(dp[i+1][j+1],ans); 
		}
	}
	cout<<n+m-2*ans<<endl;
	}
return 0;}

D.Epic Transformation

给一个长度为n的整数序列,选择两个不同的数字从数组中删去。求进行若干次操作后,数组的最小长度.
思路: 贪心。显然是删去次数比较多的好,免得后面都堆一块删不掉了。然后注意用map记录下每个数字的出现情况。因为数字比较大需要离散化,再把map遍历一次录入优先队列中,然后模拟删除即可。

#include
using namespace std;
map<int,int> v;
int main(){
	int T;cin>>T;
	while(T--){
		int n;cin>>n;v.clear();
		for(int i=0;i<n;i++) {
			int temp;scanf("%d",&temp);
			v[temp]++;
		}
			int ans=0;priority_queue<pair<int,int> > q;
		for(auto i : v){
			int x1=i.first; int x2=i.second;
			q.push(make_pair(x2,x1));
		}
		while(q.size()>=2){
			int x1=q.top().first;int x2=q.top().second;q.pop();
			int y1=q.top().first;int y2=q.top().second;q.pop();
			x1--;y1--;
			if(x1) q.push(make_pair(x1,x2));
			if(y1) q.push(make_pair(y1,y2));
			ans+=2;
		}
		cout<<n-ans<<endl;
	}
return 0;}

E. Restoring the Permutation

题意: 对于一个n的全排列(只有1~n的数字组成)P. 进行以下操作可以得到一个序列 q . qi=max(p1,p2,p3~~~~~,pi) .显然(题目暗示)一个 P是对应一个 序列q的,但反过来却不是。现在给你一个序列q,要你求可能还原回去的序列P.
输出字典序最大的序列P与字典序序列最小的序列P。
思路: 好家伙,n=2e5,我tm直接开始思考构造.字典序最大的很好构造.一个显然的性质是,qi序列一定是不递减的。每次出现新的qi(与前面不同),代表这个序列增加了“可用”的数字。我们用一个set维护它。每当加入一个新的qi,把(qi-1)+1 到 qi 全部加入set 然后每次都挑选set里面的最大值(set自带有序)。由于贪心的关系,每次我们都会选最大值。所以之前多加的那些数字是不会被选到的。
但是,在构造字典序最小的时候直接暴力地(qi-1)+1 到 qi 全加是不可取的。因为这时候会选了还没“应该出现”的数字 . 比如你第一个是 3 直接把1——3全加,你就会选了1. 这是不科学的.正解是用一个变量now指向当前最小值,开一个vis数组标记当前数字是否用过.然后当qi+1与qi相同时,说明没有出现新的最大值,就把还没出现的最小的now加入set.
由于每个元素只会加入集合一次 大概是O(nlogn)吧

#include
using namespace std;
const int maxn = 2e5+5;
int q[maxn];int ans_min[maxn];int ans_max[maxn];bool vis[maxn];
int main(){
	int T;cin>>T;
	while(T--){
		int n;cin>>n;
		for(int i=0;i<n;i++) scanf("%d",&q[i]);
		memset(vis,0,sizeof(vis));
		set<int> s; int now_max=0;int now=1;
		for(int i=0;i<n;i++) {
			bool flag=false;int pre=now_max;
			if(q[i]>now_max) { flag=true; now_max=q[i];
			} 
			if(flag) { 
				s.insert(now_max);vis[now_max]=true;  
			}
			else {
				while(vis[now]&&now<n) now++;
				vis[now]=true;s.insert(now);
			}
			set<int>::iterator p;
			p=s.begin(); 
			ans_min[i]=*p;
			s.erase(p);
		}
			for(int i=0;i<n;i++) {
			if(i==0) cout<<ans_min[i];
			else cout<<' '<<ans_min[i];
		}
		cout<<endl;
		s.clear();now_max=0;
		for(int i=0;i<n;i++) {
			bool flag=false;int pre=now_max;
			if(q[i]>now_max) { flag=true; now_max=q[i];
			} 
			if(flag) {
				for(int j=pre+1;j<=now_max;j++) s.insert(j);
			}
			set<int>::iterator p;
			p=s.end(); 
			ans_max[i]=*(--p);
			s.erase(p);
		}
		for(int i=0;i<n;i++) {
			if(i==0) cout<<ans_max[i];
			else cout<<' '<<ans_max[i];
		}
		cout<<endl;
		
			}
return 0;}

F. Triangular Paths

题意 给你一个无限二叉树,但是这个二叉树每层的结点就是它的层数.1层有一个结点,2层有两个结点.然后呢每个结点有一个坐标(r,c)r代表这个结点层数,c代表这个结点是这层第c个。现在呢,每个结点有两条路,如果你r+c是个偶数,那么你可以免费通向(r+1,c)(左边),或者花一点cost去(r+1,c+1). r+c是奇数就反过来,免费右走,付费左走。现在呢,在给你n个结点,问你从(1,1)出发跑完这些结点最少花费是多少.(结点数 n=2e5).
思路: 好家伙,二叉树还整那么多结点,根据lrj指导,我直接开始找规律。注意到偶数变奇数不用给钱,而且奇数如果不给钱就一直是奇数.
这点性质非常有用,说明如果我们要花钱,一定是花在了维护一个点是偶数的情况。什么时候要维护一个点是偶数? 没错,就是让它的坐标 C不增加。因为奇数让c+1是不用付费的。记当前结点为(r1,c1) 要去的结点是(r2,c2)
决策次数一共是r2-r1次. 记r3=r2-r1; c3=c2-c1; _even=r3-c3. _even就是一共需要让c停下来的次数. 如果开始r1+c1就是偶数那么我们还能白嫖一次停下来的钱,故 even–; 然后我们发现每次花费1,实际上可以停两次.奇数->偶数->奇数,这两步中,r+2了,但c却仍保持不变.然后统计花费即可. 复杂度 应该非常可观.
另外,还有一个特例,就是题目里面那个1 和10000~~那个。因为没有多余的动作把偶数转换为奇数,所以只能一直花费,记得特判以下。条件就是

(pre_c+pre_r)%2==0&&_even==0
#include
using namespace std;
const int maxn = 2e5+2;
struct Node{
	int r,c;
	bool operator < (const Node &x) const  {
		return r<x.r;
	}
}a[maxn];
int main(){
	int T;cin>>T;
	while(T--){
		int n; cin>>n;
		for(int i=0;i<n;i++) {
			scanf("%d",&a[i].r);
		}
		for(int i=0;i<n;i++) scanf("%d",&a[i].c);
		sort(a,a+n);
		int pre_r=1;int pre_c=1;long long cost=0;
		for(int i=0;i<n;i++){
		int	delta_c=a[i].c-pre_c;int delta_r=a[i].r-pre_r;
		if(delta_r==0) continue;
		int _even=delta_r-delta_c; 
		 if((pre_c+pre_r)%2==0&&_even==0) cost+=delta_r;
		 else {
		 	if((pre_r+pre_c)%2==0) _even--;
		 	if(_even>0) if(_even%2==0) cost+=_even/2; else cost+=(_even/2)+1;
		 }
		 pre_c=a[i].c;pre_r=a[i].r;    
		}
		cout<<cost<<endl; 
	}
return 0;}

G. Maximize the Remaining String

题意:给一个字符串(长度n=2e5) s,每次可以进行以下操作,要求得到字符串t字典序最大.
操作:挑选一个字母至少出现过两次的,删去其中任意一个.
最后要求得到一个字符串t,s中有的元素t都有.然后字符串t中的字母是唯一的,是一个集合。
思路:跪了跪了. 参考答案了.删除太复杂了,我们想要挑选,因为挑选才可能出现字典序最大嘛。我们说挑选某个元素 si 那么,他后面一定还有 si.为啥? 你是怎么挑选的,不就是把后面相同的si元素删光然后留下这个吗.
将s字符串加入一个集合s. 每次遍历这个集合找到一个合法的最大字母.把这个字母加入到答案字符串t中。
怎么样的字母才算合法呢? 就是选完这个元素后,剩下的右边那一串的元素种类是m - 1 (m是当前串仍剩余元素的种类). 每次操作完,为了避免重复挑选,还要把右边那一串删去挑选元素作为下一次考量的字符串s.
因为需要位置合法,你不能挑完一个数,再去挑前面的那些,你是去挑后面的那些.
所以需要一个cal函数计算某字符串的元素种类.使用了unique 和sort写.
然后一个构造字符串的函数,构造符合条件的字符串(右边有m-1个元素).

#include
using namespace std;
const int maxn = 2e5+5;
int cal(string s){
	sort(s.begin(),s.end());
	return unique(s.begin(),s.end())-s.begin();
}
string _build(string s,char ch){
	string t;bool first=false;
	int n=s.size();
	for(int i=0;i<n;i++){
		if(s[i]!=ch&&first) t+=s[i];
		else if(s[i]==ch) first=true;
	}
	return t;
}
int main(){
	int T;cin>>T;
	while(T--){
		string s;cin>>s;
		unordered_set<char> ss;
		for( auto p : s) ss.insert(p);
		int m=cal(s);string t;
		while(m>0) {
			char _max=0;
			for(auto p : ss) {
				if ( cal(_build(s,p)) == m-1  ){
					_max=max(_max,p);
				}
			}
			t+=_max; m--;
			s=_build(s,_max);
			ss.erase(_max);
		}
		cout<<t<<endl;
	}
return 0;}

第一次写… 不好的多包涵

你可能感兴趣的:(CF)