Codeforces Round 890 div2 A-E1 个人练习

A. Tales of a Sort

题意:
有一个长度为 n n n 的正整数数组 a a a
定义操作:

  • ∀ i ∈ [ 1 , n ] , 将 a i 替换为 m a x ( 0 , a i − 1 ) \forall i \in [1,n] ,\quad 将 a_i 替换为 max(0,a_i-1) i[1,n],ai替换为max(0,ai1)

问最少要多少次操作才能将 a a a 数组变为非递减

思路:
操作等价于将数组中所有正数减 1 1 1,减到 0 0 0 的保持不变
对于一对 a i > a i + 1 a_i > a_{i+1} ai>ai+1,最后一定要将 a i a_i ai 减为 0 0 0 才符合非递减的要求,需要 a i a_i ai 次操作
因此统计所有这样的配对里面最大 a i a_i ai 即可

// Problem: A. Tales of a Sort
// Contest: Codeforces - Codeforces Round 890 (Div. 2) supported by Constructor Institute
// URL: https://codeforces.com/contest/1856/problem/A
// 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;

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 solve(){
	int n=read();
	std::vector<int> a(n+1);
	int maxv=0;
	fore(i,1,n+1)	a[i]=read();
	for(int i=n-1;i>=0;--i)
		if(a[i]>a[i+1])
			maxv=std::max(maxv,a[i]);
	return maxv;
}

int main(){
    int t=read();
    while(t--){
    	std::cout<<solve()<<endl;
    }
	return 0; 
}

B. Good Arrays

题意:
有一个长度为 n n n 的正整数数组 a a a
定义另一个正整数 G o o d Good Good数组 b b b

  • ∀ i ∈ [ 1 , n ] , a i ≠ b i \forall i \in [1,n],a_i \neq b_i i[1,n]ai=bi
  • ∑ a i = ∑ b i \sum a_i = \sum b_i ai=bi

问对于给定的 a a a ,是否存在对应的 G o o d Good Good 数组 b b b

思路:
如果 a i = 1 a_i = 1 ai=1 ,那么 b i > 1 b_i >1 bi>1
考虑先将 a a a 中所有大于 1 1 1 a i a_i ai 变为 1 1 1 ,并记录这多余的差值 r e s res res,将其转移到其他 a i = 1 a_i = 1 ai=1 的位置,令这些位置变成 2 2 2

这样就保证了 ∀ i ∈ [ 1 , n ] , a i ≠ b i \forall i \in [1,n],a_i \neq b_i i[1,n]ai=bi

如果 r e s res res 够用,就一定存在,否则就不存在

// Problem: B. Good Arrays
// Contest: Codeforces - Codeforces Round 890 (Div. 2) supported by Constructor Institute
// URL: https://codeforces.com/contest/1856/problem/B
// 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;

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;
}

bool solve(){
	int n=read();
	std::vector<int> a(n+1);
	fore(i,1,n+1)	a[i]=read();
	if(n==1)	return false;
	ll res=0;
	fore(i,1,n+1)
		res+=a[i]-1;
	fore(i,1,n+1)
		if(a[i]==1){
			if(!res)	return false;
			--res;
		}
	return true;
}

int main(){
    int t=read();
    while(t--){
    	std::cout<<(solve()?"YES":"NO")<<endl;
    }
	return 0; 
}

C. To Become Max

题意:
给定一个长度为 n n n 的正整数数组 a a a
定义一个操作:

  • 挑选一个 1 ≤ i ≤ n − 1 且 a i ≤ a i + 1 的位置,将 a i 的值 + 1 挑选一个 1 \leq i \leq n-1 且 a_i \leq a_{i+1}的位置,将 a_i 的值 +1 挑选一个1in1aiai+1的位置,将ai的值+1

最多操作 k k k 次,问最后数组中的最大元素

思路:
最终答案的下界是 数组中最大的元素,上界是 数组中最大的元素 + k k k(不一定能取到)
如果某个答案可行,那么比它小的答案一定可行,满足二分的单调性
考虑二分答案,对每个答案检查可行性

枚举每个位置作为最终答案,也就是最高的点
从这个位置往后,一定满足当前高度 h h h 1 1 1 为递减高度,逐个递减
一旦找到一个 a i ≥ h a_i \geq h aih ,这个答案就是可行的

时间复杂度 : O ( n 2 l o g n ) O(n^2 logn) O(n2logn)

// Problem: C. To Become Max
// Contest: Codeforces - Codeforces Round 890 (Div. 2) supported by Constructor Institute
// URL: https://codeforces.com/contest/1856/problem/C
// Memory Limit: 256 MB
// Time Limit: 2000 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;
}

const int N=1005;
int a[N];
int n;

bool check(int x,int k){
	fore(p,1,n){	//最高点
		int res=k;	//剩余操作次数
		int h=x;	//当前位置应该到达的高度
		fore(i,p,n+1){
			if(a[i]>=h)	return true;
			if(res<h-a[i])	break;
			res-=h-a[i];
			--h;
		}
	}
	return false;
}

int main(){
    int t=read();
    while(t--){
    	n=read();
    	int k=read();
    	int l=0,r=0;
    	fore(i,1,n+1){
    		a[i]=read();
    		l=std::max(l,a[i]);
    	}
    	r=l+k;
    	int ans=l++;
    	while(l<=r){
    		int mid=l+r>>1;
    		if(check(mid,k)){
    			ans=mid;
    			l=mid+1;
    		}
    		else r=mid-1;
    	}
    	std::cout<<ans<<endl;
    }
	return 0; 
}

D. More Wrong

题意:
一道交互题
有一个排列,每一次可以询问区间 [ l , r ] [l,r] [l,r] 的逆序对的数量,并花费 ( r − l ) 2 (r-l)^2 (rl)2 个硬币
要求在不超过 5 n 2 5n^2 5n2 花销的前提下找出 排列中最大元素的下标

思路:
定义 q ( l , r ) q(l,r) q(l,r) [ l , r ] [l,r] [l,r] 这个区间里面逆序对的数量
由于是排列( 1 − n 1-n 1n 仅出现一次),因此有这样的一个性质

  • q ( l , r − 1 ) = q ( l , r )    ⟺    r 就是 [ l , r ] 最大元素的下标 q(l,r-1) = q(l,r) \iff r就是 [l,r] 最大元素的下标 q(l,r1)=q(l,r)r就是[l,r]最大元素的下标

利用这个性质,我们可以分治,将大问题分成小问题解决:
对于一个区间 [ l , r ] [l,r] [l,r] ,设 a a a [ l , m i d ] [l,mid] [l,mid] 的最大元素的下标, b b b [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 的最大元素的下标
询问 q ( l , b − 1 ) q(l,b-1) q(l,b1) q ( l , b ) q(l,b) q(l,b),如果二者相等,那么 b b b 就是区间 [ l , r ] [l,r] [l,r] 的最大元素的下标
反之就是 a a a
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

关于硬币消耗的证明,可以参考 Codeforces Tutorial

// Problem: D. More Wrong
// Contest: Codeforces - Codeforces Round 890 (Div. 2) supported by Constructor Institute
// URL: https://codeforces.com/contest/1856/problem/D
// Memory Limit: 256 MB
// Time Limit: 2000 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 query(int l,int r){
	if(l==r) return 0;
	std::cout<<"? "<<l<<' '<<r<<endl;
	std::cout.flush();
	int res;
	std::cin>>res;
	return res;
}

int solve(int l,int r){
	if(l==r)	return l;
	int mid=l+r>>1;
	int a=solve(l,mid);
	int b=solve(mid+1,r);
	
	int r1,r2;
	r1=query(a,b-1);
	r2=query(a,b);
	if(r1==r2)	return b;
	return a;
}

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;
    	int ans=solve(1,n);
    	std::cout<<"! "<<ans<<endl;
    	std::cout.flush();
    }
	return 0; 
}

E1. PermuTree (easy version)

题意:
给定一个 n n n 个节点的以 1 1 1 为根的 T r e e Tree Tree
对于一个排列,定义 f ( a ) f(a) f(a) 为满足 a u < a l c a ( u , v ) < a v a_u < a_{lca(u,v)} < a_v au<alca(u,v)<av ( u , v ) (u,v) (u,v) 对的数量
找出 f ( a ) f(a) f(a) 的最大值

思路:
对于一个可以作为 l c a lca lca 的节点 x x x 来说,它贡献的 f ( a ) f(a) f(a) 的值就是它的所有儿子中:
值小于 a x a_x ax 的节点的数量 × \times × 值大于 a x a_x ax 的节点的数量
因此问题转化为怎样划分 x x x 的儿子,使得这个贡献尽可能大,所有贡献加起来就是答案

x x x m m m 个儿子分支,第 i i i 个儿子分支有 s i s_i si 个儿子和 b i b_i bi 个值小于 a x a_x ax 的节点 ( 0 ≤ b i ≤ s i ) (0 \leq b_i \leq s_i) (0bisi)
那么对于 x x x 这颗子树,它的贡献就是:
( s 1 − b 1 ) ( 0 + b 2 + b 3 + . . . + b m ) + ( s 2 − b 2 ) ( b 1 + 0 + b 3 + . . . + b m ) + . . . + ( s m − b m ) ( b 1 + b 2 + . . . + 0 ) (s_1-b_1)(0+b_2+b_3+...+b_m)+(s_2-b_2)(b_1+0+b_3+...+b_m)+...+(s_m-b_m)(b_1+b_2+...+0) (s1b1)(0+b2+b3+...+bm)+(s2b2)(b1+0+b3+...+bm)+...+(smbm)(b1+b2+...+0)

考虑 01 背包 01背包 01背包 进行划分,使得每个节点的贡献最大
对于每个能作为 L C A LCA LCA 的节点 x x x
定义 d p [ i ] [ B ] dp[i][B] dp[i][B] 为前 i i i 颗子树里选 B B B 个节点作为值小于 a x a_x ax 的最大贡献
定义 S i = ∑ j = 1 i s j S_i = \sum_{j=1}^{i} s_j Si=j=1isj 为前 i i i 颗子树的儿子总数
定义 B i = ∑ j = 1 i b j B_i = \sum_{j=1}^{i} b_j Bi=j=1ibj 为前 i i i 颗子树的值小于 a x a_x ax 的儿子数

可以得到状态转移方程:

  • d p [ 1 ] [ B ] = 0 , f o r 0 ≤ B ≤ s 1 dp[1][B] = 0 , for 0 \leq B \leq s_1 dp[1][B]=0,for0Bs1
  • d p [ i ] [ B ] = m a x ( d p [ i − 1 ] [ B − b i ] + b i × ( S i − 1 − ( B i − b i ) ) + ( s i − b i ) × ( B i − b i ) ) dp[i][B] = max(dp[i-1][B-b_i] + b_i \times (S_{i-1}-(B_i-b_i))+(s_i-b_i) \times (B_i-b_i)) dp[i][B]=max(dp[i1][Bbi]+bi×(Si1(Bibi))+(sibi)×(Bibi))
    f o r 0 ≤ B ≤ S i , m a x ( 0 , B i − S i − 1 ) ≤ b i ≤ m i n ( s i , B i ) for\quad 0 \leq B \leq S_i \quad ,max(0,B_i-S_{i-1} ) \leq b_i \leq min(s_i,B_i) for0BSimax(0,BiSi1)bimin(si,Bi)

b i b_i bi 的取值范围是因为:

  • 0 ≤ b i ≤ s i 0 \leq b_i \leq s_i 0bisi
  • 0 ≤ B i − b i ≤ S i − 1 0 \leq B_i - b_i \leq S_{i-1} 0BibiSi1

可以利用 01 背包 01背包 01背包 的一些操作将 d p dp dp 数组压到一维,这时遍历 B i B_i Bi 必须递减 b i b_i bi 必须递增
这样才不会破坏 i − 1 i-1 i1 的状态:如果 b i = 0 b_i=0 bi=0 ,这时 d p [ i − 1 ] [ B − b i ] = d p [ i − 1 ] [ B ] dp[i-1][B-b_i]=dp[i-1][B] dp[i1][Bbi]=dp[i1][B]
因此要先考虑 b i b_i bi 较小的情况,避免把 i − 1 i-1 i1 状态的 d p [ B ] dp[B] dp[B] 更新为 i i i 的状态被后续使用

时间复杂度: O ( n 2 ) O(n^2) O(n2)
证明可以参考 Codeforces Tutorial
还有一些大佬的 blog

// Problem: E1. PermuTree (easy version)
// Contest: Codeforces - Codeforces Round 890 (Div. 2) supported by Constructor Institute
// URL: https://codeforces.com/contest/1856/problem/E1
// Memory Limit: 512 MB
// Time Limit: 2000 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;
}

const int N=6050;
std::vector<int> g[N];
int s[N];	//节点i的儿子数
ll ans;

void dfs(int u,int fa=-1){
	s[u]=1;
	for(auto v:g[u])
		if(v!=fa){
			dfs(v,u);
			s[u]+=s[v];
		}
	std::vector<ll> dp(s[u],0);
	ll preS=0;	//前i-1颗子树的儿子总数
	for(auto v:g[u]){	//前i颗子树
		ll si=s[v];
		for(ll B=preS+si;B>=0;--B){	//前i颗子树有B个小于
					/* 第i颗子树有bi个小于 注意这里bi必须从小到大遍历*/
			for(ll bi=std::max(0ll,B-preS);bi<=std::min(si,B);++bi){
				dp[B]=std::max(dp[B],dp[B-bi]+bi*(preS-(B-bi))+(si-bi)*(B-bi));
			}
		}
		preS+=si;
	}
	ans+=*max_element(dp.begin(),dp.end());
	dp.clear();
}

int main(){
    int n=read();
    fore(i,1,n){
    	int x=read();
    	g[x-1].push_back(i);
    }
    dfs(0);
    std::cout<<ans;
	return 0; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值