Codeforces Round 892 div2 A-E 个人练习

A. United We Stand

题意:
给定一个长度为 n n n 的正整数数组 a a a
a a a 中的所有元素分配到 b b b c c c 中,要求:

  • b b b c c c 不空
  • ∀ i ∈ [ 1 , l b ] , j ∈ [ 1 , l c ] , c j ∤ b i \forall i\in [1,l_b], j\in [1,l_c],\quad c_j \nmid b_i i[1,lb],j[1,lc],cjbi

输出符合条件的 b b b c c c

思路:
观察可以发现:可以直接把 a a a 中的最大值放到 c c c 中,因为 c j ∣ b i → c j ≤ b i c_j \mid b_i \rightarrow c_j \leq b_i cjbicjbi
如果 c c c 里面只有一种元素( a a a 中的最大值),那么显然是符合条件的

// Problem: A. United We Stand
// Contest: Codeforces - Codeforces Round 892 (Div. 2)
// URL: https://codeforces.com/contest/1859/problem/0
// Memory Limit: 256 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

const int N=200;
int a[N],b[N],c[N];

void solve(){
	int n;
	std::cin>>n;
	int cnt=0,maxv=0;
	fore(i,1,n+1){
		std::cin>>a[i];
		if(a[i]>maxv){
			maxv=a[i];
			cnt=1;
		}
		else if(a[i]==maxv)	++cnt;
	}
	if(cnt==n){
		std::cout<<-1<<endl;
		return;
	}
	std::cout<<n-cnt<<' '<<cnt<<endl;
	fore(i,1,n+1)
		if(a[i]!=maxv)	std::cout<<a[i]<<" ";
	std::cout<<endl;
	fore(i,1,cnt+1)	std::cout<<maxv<<" ";
	std::cout<<endl;
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	solve();
    }
	return 0; 
}

B. Olya and Game with Arrays

题意:
n n n 个数组,每个数组有 m i m_i mi 个数字,每个数组最多可以移走一个数字到其他组,同一个组可以接受多个数字
定义 b e a u t y : ∑ i = 1 n m i n j = 1 m i a i j beauty: \sum_{i=1}^{n} min_{j=1}^{m_i} a_{ij} beauty:i=1nminj=1miaij

求出最大 b e a u t y beauty beauty

思路:
要注意到:一个数组中,只有最小值次小值会对答案有贡献,因为每个数组最多只能移走一个数字
并且将一个数组中的数字移动到另外一个数组,不会增加那个数组最小值对答案的贡献,只有可能减少

对于所有数组中的最小的那个值 m i n v minv minv,把它移动到那个数组,那个数组对答案的贡献都是 m i n v minv minv
当这个数组中最小的值被移走了,次小值就成为了对答案的贡献
因此可以考虑把 m i n v minv minv 移动到次小值最小的那个数组中,这样答案就是 m i n v + s u m − m i n ′ minv + sum - min^\prime minv+summin
s u m sum sum 是所有数组中次小值之和, m i n ′ min^\prime min 是最小的次小值

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

const int N=25050;
ll minv;

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	minv=INF;
    	int n;
    	std::cin>>n;
    	std::vector<ll> minsec(n);
    	int m;
    	fore(i,0,n){
    		std::cin>>m;
    		std::vector<ll> v(m);
    		fore(j,0,m)	std::cin>>v[j];
    		ll temp=*min_element(v.begin(),v.end());
    		minv=std::min(minv,temp);
    		v.erase(find(v.begin(),v.end(),temp));
    		minsec[i]=*min_element(v.begin(),v.end());
    	}
    	std::cout<<minv+accumulate(minsec.begin(),minsec.end(),0ll)-*min_element(minsec.begin(),minsec.end())<<endl;
    }
	return 0; 
}

C. Another Permutation Problem

题意:
对于所有长度为 n n n排列,求出 ( ∑ i = 1 n p i ∗ i ) − ( m a x j = 1 n p j ∗ j ) (\sum_{i=1}^{n}p_i*i ) - (max_{j=1}^{n} p_j*j) (i=1npii)(maxj=1npjj) 的最大值

思路:
打表发现符合条件的排列一定是先上升: 1 , 2 , 3.... 1,2,3.... 1,2,3....,然后到了某个位置以 n n n 递减: n , n − 1 , n − 2.... n,n-1,n-2.... n,n1,n2....,一直到末尾
直接枚举这个位置就好

// Problem: C. Another Permutation Problem
// Contest: Codeforces - Codeforces Round 892 (Div. 2)
// URL: https://codeforces.com/contest/1859/problem/C
// Memory Limit: 256 MB
// Time Limit: 3000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

inline int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')	f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		x=x*10+c-'0';
		c=getchar();
	}
	return f*x;
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	int n;
    	std::cin>>n;
    	std::vector<int> a(n+5);
    	fore(i,1,n+1)	a[i]=i;
    	int ans=0;
    	for(int p=n;p>=1;--p){
    		int v=n;
    		fore(i,p,n+1)	a[i]=v--;
    		int res=0,maxv=0;
    		fore(i,1,n+1){
    			res+=i*a[i];
    			maxv=std::max(maxv,i*a[i]);
    		}
    		ans=std::max(ans,res-maxv);
    	}
    	std::cout<<ans<<endl;
    }
	return 0; 
}

D. Andrey and Escape from Capygrad

题意:
给定 n n n 个线段,每个线段有 l i , r i , a i , b i ( 1 ≤ l i ≤ a i ≤ b i ≤ r i ≤ 1 0 9 ) l_i,r_i,a_i,b_i \quad(1 \leq l_i \leq a_i \leq b_i \leq r_i \leq 10^9) li,ri,ai,bi(1liaibiri109),表示在 [ l i , r i ] [l_i,r_i] [li,ri] 的点可以传送到 [ a i , b i ] [a_i,b_i] [ai,bi] 中的任何一个点

q q q 次询问,每次询问给定一个坐标 x i x_i xi ,要求回答从这个坐标出发能到达的最远位置

思路:
考虑扫描线,从最右边开始往左边扫描
不难注意到,每一次传送一定是传送到 b i b_i bi ,这样是最优的
而一个区间 b i → r i b_i \rightarrow r_i biri 这一段如果只有这个区间的话,肯定是留在原地更好,如果有别的区间,就要考虑能不能去到更远的地方

所以我们只要注意三个变量: b i , x i , l i b_i, x_i , l_i bi,xi,li
并且这三个变量的优先级 是: b i > x i > l i b_i > x_i > l_i bi>xi>li
可以参考第三个样例,必须先更新一些能传送的点或者答案

定义 a n s [ i ] ans[i] ans[i] :表示第 i i i 个区间能到的最远位置
定义 q u e r y [ i ] query[i] query[i] :表示每个询问的答案,初始化为原始坐标 x i x_i xi

使用 m u l t i s e t multiset multiset 来实现扫描活动

// Problem: D. Andrey and Escape from Capygrad
// Contest: Codeforces - Codeforces Round 892 (Div. 2)
// URL: https://codeforces.com/contest/1859/problem/D
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<vector>
#include<algorithm>
#include<set>
#include<unordered_set>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	int n;
    	std::cin>>n;
    	std::vector<std::tuple<int,int,int>> event;
    	std::vector<int> ans(n);
    	fore(i,0,n){
    		int l,r,a,b;
    		std::cin>>l>>r>>a>>b;
    		ans[i]=b;
    		event.emplace_back(b,3,i);	//第二个元素是活动种类
    		event.emplace_back(l,1,i);	//注意第二个元素的标号必须注意优先级
    	}
    	int q;
    	std::cin>>q;
    	std::vector<int> query(q);	//答案
    	fore(i,0,q){
    		int x;
    		std::cin>>x;
    		query[i]=x;	//初始化为原始位置
    		event.emplace_back(x,2,i);
    	}
    	std::sort(event.begin(),event.end());
    	reverse(event.begin(),event.end());
    	std::multiset<int> s;
    	for(auto [x,type,id] : event){
    		if(type==3){	//bi
    			if(!s.empty())	ans[id]=*s.rbegin();
    			s.insert(ans[id]);
    		}
    		else if(type==1){	//li
    			s.extract(ans[id]);	//只删除一个,用erase会全部删除
    		}
    		else{	//xi
    			if(!s.empty())	query[id]=std::max(query[id],*s.rbegin());
    		}
    	}
    	for(auto el:query)	std::cout<<el<<' ';
    	std::cout<<endl;
    }
	return 0; 
}

E. Maximum Monogonosity

题意:
给定两个长度为 n n n 的数组 a a a b b b,定义一个区间 [ l , r ] [l,r] [l,r] c o s t cost cost

  • c o s t = ∣ b l − a r ∣ + ∣ b r − a l ∣ cost = |b_l-a_r| + |b_r-a_l| cost=blar+bral

求出最大的不相交的区间的 ∑ c o s t \sum cost cost ,并且这些区间的长度和等于 k k k

思路:
考虑 D P DP DP: 定义区间 [ l , r ] [l,r] [l,r] c o s t cost cost f ( l , r ) = ∣ b l − a r ∣ + ∣ b r − a l ∣ f(l,r) = |b_l-a_r| + |b_r-a_l| f(l,r)=blar+bral

那么对于 d p [ n 1 ] [ k 1 ] dp[n1][k1] dp[n1][k1],状态转移方程可以写成:
d p [ n 1 ] [ k 1 ] = m a x ( d p [ n 1 − 1 ] [ k 1 ] , d p [ n 1 − i ] [ k 1 − i ] + f ( n 1 − i + 1 , n 1 ) , 1 ≤ i ≤ k 1 ) dp[n1][k1] = max(dp[n1-1][k1],dp[n1-i][k1-i]+f(n1-i+1,n1),1\leq i \leq k1) dp[n1][k1]=max(dp[n11][k1],dp[n1i][k1i]+f(n1i+1,n1),1ik1)

方程的第二个式子表示:从 n 1 n1 n1 开始往前连续选 i i i 个元素作为一个区间,然后再在前面选 k 1 − i k1-i k1i 个元素
这样的时间复杂度是 O ( n k 2 ) O(nk^2) O(nk2) ,会 T L E TLE TLE

考虑优化,发现当 n 1 − k 1 = d i a g v a l n1-k1 = diag_val n1k1=diagval 这样一个特定值时,方程的第二个式子会重复计算这条对角线上的最大值:

1
因此可以维护一下这条对角线上的值,从而加速状态转移

c o s t cost cost 的两个绝对值拆开,有四种情况:

  • b l − a l − a r + b r b_l - a_l -a_r + b_r blalar+br
  • b l + a l − a r − b r b_l + a_l - a_r -b_r bl+alarbr
  • − b l − a l + a r + b r -b_l - a_l + a_r + b_r blal+ar+br
  • − b l + a l + a r − b r -b_l + a_l +a_r - b_r bl+al+arbr

考虑分别在线维护四种情况的最大值 m x 1 , m x 2 , m x 3 , m x 4 mx_1,mx_2,mx_3,mx_4 mx1,mx2,mx3,mx4

注意到我们应该尽可能使前面两个数之和更大,因为每次最后两个数就是对应状态转移方程第二个式子的区间右端点 ( a n 1 , b n 1 ) (a_{n1},b_{n1}) (an1,bn1) ,是固定的,我们只能选择最后一个区间的左端点,使得四个 m x mx mx 值尽可能大

注意 i 、 j i 、j ij 一定要从 0 0 0 开始遍历,否则无法处理 n 1 = k 1 n1=k1 n1=k1区间全选的情况

时间复杂度优化到: O ( n 2 ) O(n^2) O(n2)

// Problem: E. Maximum Monogonosity
// Contest: Codeforces - Codeforces Round 892 (Div. 2)
// URL: https://codeforces.com/contest/1859/problem/E
// Memory Limit: 512 MB
// Time Limit: 2500 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

void solve(){
	int n,k;
	std::cin>>n>>k;
	std::vector<std::vector<ll>> dp(n+5,std::vector<ll>(k+5,0));
	std::vector<ll> a(n+5),b(n+5);
	fore(i,1,n+1)	std::cin>>a[i];
	fore(i,1,n+1)	std::cin>>b[i];
	std::vector<ll> mx1(n+5,-INFLL);
	std::vector<ll> mx2(n+5,-INFLL);
	std::vector<ll> mx3(n+5,-INFLL);
	std::vector<ll> mx4(n+5,-INFLL);
	fore(i,0,n+1)
		fore(j,0,std::min(i,k)+1){
			int diag_val=i-j;
			if(i){
				dp[i][j]=std::max(dp[i-1][j],-a[i]+b[i]+mx1[diag_val]);	//这里要先和上一轮比较,直接从前i-1选j个
				dp[i][j]=std::max(dp[i][j],-a[i]-b[i]+mx2[diag_val]);
				dp[i][j]=std::max(dp[i][j],a[i]+b[i]+mx3[diag_val]);
				dp[i][j]=std::max(dp[i][j],a[i]-b[i]+mx4[diag_val]);
			}
			
			/* 更新对角最值 */
			mx1[diag_val]=std::max(mx1[diag_val],dp[i][j]-a[i+1]+b[i+1]);
			mx2[diag_val]=std::max(mx2[diag_val],dp[i][j]+a[i+1]+b[i+1]);
			mx3[diag_val]=std::max(mx3[diag_val],dp[i][j]-a[i+1]-b[i+1]);
			mx4[diag_val]=std::max(mx4[diag_val],dp[i][j]+a[i+1]-b[i+1]);
		}
	std::cout<<dp[n][k]<<endl;
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	solve();
    }
	return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值