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,ai−1)
问最少要多少次操作才能将 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 挑选一个1≤i≤n−1且ai≤ai+1的位置,将ai的值+1
最多操作 k k k 次,问最后数组中的最大元素
思路:
最终答案的下界是 数组中最大的元素,上界是 数组中最大的元素 +
k
k
k(不一定能取到)
如果某个答案可行,那么比它小的答案一定可行,满足二分的单调性
考虑二分答案,对每个答案检查可行性
枚举每个位置作为最终答案,也就是最高的点
从这个位置往后,一定满足当前高度
h
h
h 以
1
1
1 为递减高度,逐个递减
一旦找到一个
a
i
≥
h
a_i \geq h
ai≥h ,这个答案就是可行的
时间复杂度 : 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
(r−l)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
1−n 仅出现一次),因此有这样的一个性质
- q ( l , r − 1 ) = q ( l , r ) ⟺ r 就是 [ l , r ] 最大元素的下标 q(l,r-1) = q(l,r) \iff r就是 [l,r] 最大元素的下标 q(l,r−1)=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,b−1) 和
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)
(0≤bi≤si)
那么对于
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)
(s1−b1)(0+b2+b3+...+bm)+(s2−b2)(b1+0+b3+...+bm)+...+(sm−bm)(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,for0≤B≤s1
-
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[i−1][B−bi]+bi×(Si−1−(Bi−bi))+(si−bi)×(Bi−bi))
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) for0≤B≤Si,max(0,Bi−Si−1)≤bi≤min(si,Bi)
b i b_i bi 的取值范围是因为:
- 0 ≤ b i ≤ s i 0 \leq b_i \leq s_i 0≤bi≤si
- 0 ≤ B i − b i ≤ S i − 1 0 \leq B_i - b_i \leq S_{i-1} 0≤Bi−bi≤Si−1
可以利用
01
背包
01背包
01背包 的一些操作将
d
p
dp
dp 数组压到一维,这时遍历
B
i
B_i
Bi 必须递减,
b
i
b_i
bi 必须递增
这样才不会破坏
i
−
1
i-1
i−1 的状态:如果
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[i−1][B−bi]=dp[i−1][B]
因此要先考虑
b
i
b_i
bi 较小的情况,避免把
i
−
1
i-1
i−1 状态的
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;
}