2023.5.17~18codeforeces训练记录

第一题:1759G - Restore the Permutation

大意:

输入 T(≤1e4) 表示 T 组数据。所有数据的 n 之和 ≤2e5。
每组数据输入偶数 n(2≤n≤2e5) 和长为 n/2 的数组 b(1≤b[i]≤n),下标从 1 开始。

构造一个长为 n 的 1~n 的排列 p(下标从 1 开始),满足 max(p[2*i-1],p[2*i]) = b[i]。
如果无法构造,输出 -1,否则输出字典序最小的 p。

思路1:由题意得,对于b[i],由于max(p[2*i-1],p[2*i]) = b[i],p为一个排列,故另一个数必小于b[i],为了保证字典序最小,故必有p[2*i-1]<p[2*i] ,即 p[2*i] = b[i] ;故对于每个p[2k] ,k为整数,需要寻找一个 属于[1,b[i] ) 且未被使用过的数。为了保证字典序最小,故需要从下标2n开始往1遍历,每次寻找一个属于[1,b[i] ) 且未被使用过的数中最大的数。如果找得到这样的数则依次填写直至填写完毕,则完成构造。若没找到满足要求的数,则说明无法构造。

思路2:(灵神算法交流群群友的思路) 贪心并查集。  通过union( v,v-1) 把v并到v-1的子树下,则find(v)则为第一个小于v且未被使用过的数。算法复杂度为o(n)。

思路一代码:

#include <iostream>
#include <cstring>
#include <queue> 
#include <algorithm>
#include <map>
#include <set>
#define x first
#define y second 
#define PII pair<int,int> 
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ,mod = 1e9 +7   ; 
LL a[N] ,b[N] ,w[N];
bool st[N] ;

int main()
{
	int T ;cin >> T;
	while( T--){
		
		int n ;
		cin >> n ;
		memset( st, false ,sizeof st) ;
		bool flag= true;
		for(int i= 1;i <=n/2; ++i){
			cin>> a[i*2] ;
			if( st[ a[i*2] ] ) flag =false;
			if( a[i*2] <1 || a[i*2] > 2*n ) flag =false;
			st[a[i*2] ]  = true;
		} 
		if( !flag ){ 
			cout<<-1<<endl; continue;
		}
		//cout<<"????";
		set<int> s ;
		for(int i =1; i<=n *2 ; ++i) if( !st[i]) s.insert( i ) ;
		for(int i = n ; i> 0 ; i -=2) {
			auto t = s.lower_bound( a[i]);
			//cout<< a[i] <<" "<<*t<<endl;
			if( t == s.begin()) {
				flag = false; break;
			}
			--t;
			a[i-1] = *t;
			s.erase( t) ;
		}
		if( !flag) cout<<-1<<endl;
		else {
			for(int i =1;i <= n ; ++i)
				cout<<a[i] <<" ";cout<<endl;
		}

		
		
	} 
    return 0 ; 
}



 

问题二:1693B - Fake Plastic Trees

大意:

输入 T(≤1e3) 表示 T 组数据。所有数据的 n 之和 ≤2e5。
每组数据输入 n(2≤n≤2e5) 表示一棵 n 个节点的树,编号从 1 开始,1 为根节点。
然后输入 p[2],p[3],...,p[n],其中 p[i] 表示 i 的父节点。
然后输入 n 行,其中第 i 行输入两个数 l 和 r,表示第 i 个节点值的目标范围 [l,r]。

初始时,所有节点值均为 0。
每次操作你可以选择一条从 1 开始的路径,把路径上的每个节点值都加上一个数。要求这些数按照路径的顺序,形成一个递增序列。(可以相等,可以等于 0,例如 [0,0,1,3,3])
要使所有节点值都在对应的范围内,至少要操作多少次?
思路:贪心。为了每个节点都符合要求,最糟糕的情况是每个节点都操作一次。而为了解决某个节点的同时“顺带”完成另一个节点的要求,显然,需要我们另一个节点为操作节点的父节点。故我们需要从根节点开始考虑。当考虑一个根节点时,为了保证父节点的贡献范围尽可能的大,我们可以把某个根节点的值赋为范围最大值。当节点t的子节点操作完成时,计算由子节点产生的贡献k。若k大于范围最大值,显然只能让t的值为范围最大值r[t] , 当k在范围中时, t的值只能为k;当小于范围最小值l[t]时,我们需要对该节点进行一次操作,同时为了保证父节点的贡献范围尽可能的大,需要赋值为范围最大值。当所有节点计算完成后,操作数即为结果。

代码一:dfs求解

#include <iostream>
#include <cstring>
#include <queue> 
#include <algorithm>
#include <unordered_map>
#define x first
#define y second 
#define PII pair<int,int> 
using namespace std;
typedef long long LL ;
const LL N = 3e5 +10 ,mod = 1e9 +7   ; 
LL fa[N] ;
LL w[N] ;
PII b[N] ;
int h[N] , ne[N] ,e[N] , idx ;
int ans ;

void add( int a, int b ){
	e[idx] = b , ne[idx] = h[a] , h[a] = idx ++ ;
}

LL dfs( int u ,int fa){
	LL res = 0 ;
	for(int i = h[u] ; ~i ;i =ne[i]){
		int j = e[i] ;
		res += dfs( j , u );
	}
	if( res >= b[u].x) {
		return min( res, (LL)b[u].y) ;
	}
	//cout<<"--"<<u<<" "<<res <<endl;
	ans++;
	w[u] = b[u].y;
	return w[u] ;
}

int main()
{
	int T ;cin >> T;
	while( T--){
		ans = 0 ;idx = 0;
		int n ;
		cin >> n ;
		memset( w, 0 ,sizeof w) ;
		memset( h ,-1, sizeof h ) ;
		for(int i =2; i <= n ; ++i){
			cin >>fa[i] ;
			add( fa[i] , i) ;
		}
		for(int i =1; i <=n ; ++i){
			cin >> b[i].x >> b[i].y ;
		}
		dfs( 1, -1 ) ;
		cout <<ans <<endl;
	} 
    return 0 ; 
}




代码二:拓扑排序

#include <iostream>
#include <cstring>
#include <queue> 
#include <algorithm>
#include <unordered_map>
#define x first
#define y second 
#define PII pair<int,int> 
using namespace std;
typedef long long LL ;
const LL N = 3e5 +10 ,mod = 1e9 +7   ; 
LL deg[N] ,fa[N];
LL w[N] ;
pair<LL,LL> b[N] ;
int h[N] , ne[N] ,e[N] , idx ;
int ans ;



int main()
{
	int T ;cin >> T;
	while( T--){
		ans = 0 ;idx = 0;
		int n ;
		cin >> n ;
		memset( w, 0 ,sizeof w) ;
		memset( h ,-1, sizeof h ) ;
		memset( deg, 0 , sizeof deg)  ;
		for(int i =2; i <= n ; ++i){
			int x;
			cin >>x ;
			deg[ x] ++ ; fa[i] = x ;
		}
		for(int i =1; i <=n ; ++i){
			cin >> b[i].x >> b[i].y ;
		}
		queue<int> q; 
		for(int i=1; i<=n ; ++i) if( !deg[i]) q.push( i) ;
		int res=  0; 
		while( q.size() ){
			int t = q.front( ) ;q.pop() ;
			//cout<<"--"<<t<<endl;
			if( w[t] >= b[t].x) w[t] = min( w[t] ,b[t].y) ;
			else {
				++res ; w[t] = b[t].y;
			}
			deg[ fa[t] ] --;
			w[ fa[t]] += w[t] ;
			if( !deg[fa[t]]) q.push( fa[t]) ;
		}
		cout<< res <<endl;
	} 
    return 0 ; 
}




问题三:1800D - Remove Two Letters

大意:已知长度为n的字符串s,若删除两个连续的字符,问最终能产生多少种字符串。

思路:举个例子,对于字符串aba,无论如何删除,都能产生一种。故我们考虑在什么情况下,会产生重复的一种。显然,对于连续的ai,ai+1,ai+2,若ai = ai+2,则会产生一种重复。故我们假设总共有n-1种不同字符串,每当遇到ai = ai+2时,则减去一个重复的字符串数量。

代码:

#include <iostream>
#include <cstring>
#include <queue> 
#include <algorithm>
#include <map>
#define x first
#define y second 
#define PII pair<int,int> 
using namespace std;
typedef long long LL ;
const LL N = 3e5 +10 ,mod = 1e9 +7   ; 


int main()
{
	int T ;cin >> T;
	while( T--){
		int n  ;
		cin >> n ;
		string s;
		int res = n-1;
		cin >>s ;
		for(int i =0 ; i+2 <n ; ++i){
			if( s[i] == s[i+2]) --res;
		}
		cout<< res <<endl;
	} 
    return 0 ; 
}



 

问题四:1799B - Equalize by Divide

大意:现给你一个长度为n的数组a,  现在你可以进行一次操作:选择i,j 且i!=j ,使得 a[i] = [a[i]/a[j] ,a[i]会向上取整,问需要多少次操作,才能使得a数组中所有元素都相等,并输出每次操作的i,j。

思路:每次选定数组中最大值与最小值,若两者相等,则说明完成要求。若二者不相等,①最小值为1,由于任何数除以1不变,故不可能完成要求;②最小值不为1,则令最大值赋值为  [a[i]/a[j]。重复以上步骤,直至是否完成要求。

代码:

#include <iostream>
#include <cstring>
#include <queue> 
#include <algorithm>
#include <map>
#define x first
#define y second 
#define PII pair<int,int> 
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ,mod = 1e9 +7   ; 
int a[N] ;
PII res[N] ;
int main()
{
	int T ;cin >> T;
	while( T--){
		int n ;
		cin >> n ;
		for(int i = 1; i<=n  ; ++i){
			cin >>a[i];
		}
		int cnt  = 0 ;
		
		while(1){
			int mmin = 1, mmax =1;
			for(int i=2; i<=n; ++i) {
				if( a[mmin ] > a[i]) mmin = i ;
				if( a[mmax] < a[i]) mmax = i ;
				
			}
			//cout<<mmax <<" " <<mmin<<endl;
			if( a[mmin] == a[mmax]) break;
			if( a[mmin] == 1){
				cnt = -1; break;
			}
			if( a[mmax] % a[mmin] == 0) a[mmax] /= a[mmin] ;
				else a[mmax] = a[mmax]/a[mmin] + 1;
			res[cnt++] = { mmax , mmin} ;
		}
		if( cnt == -1 || cnt == 0 ) cout<< cnt <<endl;
			else cout<<cnt <<endl;
		for(int i = 0; i <cnt; ++i)
			cout<< res[i].x <<" "<<res[i].y <<endl;

	} 
    return 0 ; 
}



 

题目五:1795C - Tea Tasting

大意:已知n个茶,n个品茶师,每个品茶师一次只能喝a[i] 份茶,每喝一次则需要向前走动一位,即第一次,第i位品茶师会品茶第i个茶,第2次会品茶第i-1个.问,所有品茶师都无法再喝茶时,每位品茶师喝了多少份茶。

思路:对于第i份茶,会被i~n的品茶师品尝,故我们需要找到从i到k位品茶师的茶量大于i的分量的k,将i~k位品茶师傅已品尝最大份量的次数都加1,若有过剩,则将剩余分量给k+1位品茶师傅。

当计算第i位的品茶师傅的品尝茶量时,用已品尝茶量+已品尝最大份量的次数* 最大品茶量a[i]即可

代码:

#include <iostream>
#include <cstring>
#include <queue> 
#include <algorithm>
#include <map>
#define x first
#define y second 
#define PII pair<int,int> 
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ,mod = 1e9 +7   ; 
LL a[N] ,b[N] ,w[N];
LL sum[N] ,p[N];

int main()
{
	int T ;cin >> T;
	while( T--){
		int n ;
		cin >> n ;
		memset( w , 0 , sizeof w) ;
		memset( p , 0, sizeof p) ;
		for(int i = 1; i<=n  ; ++i)	cin >>a[i];
		for(int i= 1; i <=n; ++i){ 
			cin >>b[i] ; sum[i]  =  sum[i-1]  +b[i ];
		}
		for(int i = 1;i <=n; ++i){
			int cnt= upper_bound( sum+i, sum+1+n , a[i] +sum[i-1]) - sum;
			p[i] += 1; p[cnt]-=1;
			w[cnt] += a[i] -sum[cnt-1]+sum[i-1] ;
			//cout<< i <<" "<<cnt-1 <<" "<<w[cnt] <<endl;
		}
		LL ce = 0;
		for(int i = 1;i <=n; ++i){
			ce += p[i] ;
			w[i] += ce*b[i] ;
			cout<<w[i] <<" " ;
		}
		cout<<endl;
		
		
	} 
    return 0 ; 
}



 

题目六:1794C - Scoring Subsequences

大意:给定长度为n的数组a, a是非递减数组。对于s[1..d],称其所有子串中 s[i..k] (1<=i,k<=d)的得分 si*si+1 *...*s[k] / (k-i+1)!   的最大值 ,需要输出对于不同的d,其对应得分最大时的子串长度。

思路:由于a非递减,对于d,最大得分最大可能是a[d]/1 ,右边界为d ,若左边界向前移动一位,则会使得分乘上a[d-1]/2 。 故可得,若向前移动 i位,会使得分乘上 a[d-i]/(i+1) 。当 a[d-i]/(i+1)<1时,显然不是最优结果。故我们可二分[1,d-1],找到第一个 a[d-i]/(i+1)>=1的i,则答案是i+1。

代码:

#include <iostream>
#include <cstring>
#include <queue> 
#include <algorithm>
#include <map>
#include <set>
#define x first
#define y second 
#define PII pair<int,int> 
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ,mod = 1e9 +7   ; 
LL a[N] ,b[N] ,w[N];
bool st[N] ;

int main()
{
	int T ;cin >> T;
	while( T--){
		
		int n ;
		cin >> n ;
		for(int i = 1 ; i <=n ; ++i)
			cin >> a[ i] ;
		cout<<1<<' ' ;
		for(int i =2 ; i<=n ; ++i){
			int l = 1,r = i-1, t =  i ;
			while( l <=r ) {
				int mid = ( l+r ) /2; 
				if( a[mid ] /(i-mid +1) >= 1) {
					t =mid;
					r= mid-1;
				}else l =mid+1;
			}
			cout<< i-t+1 <<" ";
		}
		cout<<endl;
		
	} 
    return 0 ; 
}



 

题目七:1793C - Dora and Search

大意:给定一个长度为n的排列a,输出a[l],a[r]不等于a[l..r]中的最大值和最小值时的l和r。

思路:通过哈希表,记录某个数是否已经不在[l,r]中,并维护区间的最值。从a[1,n]开始,令l=1,r=n,若a[l],a[r]不等于a[l..r]中的最大值和最小值则输出。否则,删除等于最值的边界,并更新最值,直至 l >r 或符合题意。

代码:

#include <iostream>
#include <cstring>
#include <queue> 
#include <algorithm>
#include <map>
#include <set>
#define x first
#define y second 
#define PII pair<int,int> 
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ,mod = 1e9 +7   ; 
LL a[N] ,b[N] ,w[N];
bool st[N] ;

int main()
{
	int T ;cin >> T;
	while( T--){
		
		int n ;
		cin >> n ;
		for(int i = 1 ; i <=n ; ++i)
			cin >> a[ i] ;
		memset( st, false, sizeof st) ;
		int l = 1 ,r = n , Max = n , Min = 1;
		while( l <=r ){
			//cout<<l <<" " <<r <<endl;
			if( a[r] != Max && a[r] != Min &&a[l] != Max && a[l] != Min) break;
			if( a[r] == Max || a[r] == Min) {
				st[ a[r]]  = true; r--;
			}
			if( a[l] == Max || a[l] == Min)  st[a[l]] = true,l++;
			while( st[Max] ) Max--;
			while( st[Min]) Min++;
		}
		if( l<r )  cout<< l<<" " <<r <<endl;
			else cout<<-1<<endl;
		
	} 
    return 0 ; 
}



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值