T1 排兵布阵
题目描述
在游戏中有 n n n 座城堡,每局对战由两名玩家来争夺这些城堡。每名玩家有 m m m 名士兵,可以向第 i i i 座城堡派遣 a i a_i ai 名士兵去争夺这个城堡,使得总士兵数不超过 m m m 。如果一名玩家向第 i i i 座城堡派遣的士兵数严格大于对手派遣士兵数的 2 2 2 倍,那么这名玩家就占领了这座城堡,获得 i i i 分。
现在你即将和其他 s s s 名玩家两两对战,这 s s s 场对决的派遣士兵方案必须相同。小 F 通过某些途径得知了其他 s s s 名玩家即将使用的策略并告诉了你,你应该使用某种策略策略来最大化总分。
由于方案可能不唯一,你只需要输出你能获得的总分的最大值。
输入格式
输入第一行包含三个正整数
s
,
n
,
m
s,n,m
s,n,m ,分别表示除了小 X 以外的玩家人数、城堡数
和每名玩家拥有的士兵数。
接下来
s
s
s 行,每行
n
n
n 个非负整数,表示一名玩家的策略,其中第
i
i
i 个数
a
i
a_i
ai 表示这
名玩家向第
i
i
i 座城堡派遣的士兵数。
输出格式
输出一行一个非负整数,表示你能获得的最大得分。
输入样例
2 3 6
2 2 10
0 0 0
输出样例
8
一种最佳策略为向第 1 1 1 座城堡派遣 2 2 2 名士兵,向第 2 2 2 座城堡派遣 5 5 5 名士兵,向第 3 3 3 座城堡派遣 1 1 1 名士兵。
数据范围
对于
10
%
10\%
10% 的数据,保证
s
=
1
,
n
≤
3
,
m
≤
10
s=1,n\le3,m\le10
s=1,n≤3,m≤10 。
对于
20
%
20\%
20% 的数据,保证
s
=
1
,
n
≤
10
,
m
≤
100
s=1,n≤10,m\le100
s=1,n≤10,m≤100。
对于 40% 的数据,保证
n
≤
10
,
m
≤
100
n\le10,m\le100
n≤10,m≤100 。
对于另外
20
%
20\%
20% 的数据,保证
s
=
1
s=1
s=1 。
对于
100
%
100\%
100% 的数据,保证
1
≤
s
≤
100
1\le s \le100
1≤s≤100
1
≤
n
≤
100
1\le n \le 100
1≤n≤100
1
≤
m
≤
2
∗
1
0
4
1\le m \le2*10^4
1≤m≤2∗104
对于每名玩家,
a
i
≥
0
,
∑
i
=
1
n
a
i
≤
m
a_i\ge0,\sum_{i=1}^{n}a_i\le m
ai≥0,∑i=1nai≤m
注意常数问题
解析
显然,最优的策略是在一座城市不派遣士兵或刚好派可以拿下这个城的士兵 ( x < < 1 ∣ 1 x<<1|1 x<<1∣1) 选一个。接下来就是背包问题的一点变化(背包容积:士兵总数;物品重量:选前 k k k 个所有即选到的最大的那一个;物品价值:点的编号 i ∗ k i*k i∗k(这一列击杀 k k k 人) )。DP方程见参考代码。时间复杂度 O ( n m s ) O(nms) O(nms) ,在全部取最大的时候可能会有点卡,所以合理地加 i n l i n e inline inline 和 r e g i s t e r register register , 开启读入优化是必需的。常数减小,可以安全通过。
code
#include<bits/stdc++.h>
using namespace std;
int s,n,m;
int a[123][123],aa[123];
int dp[123][20002],ss[123],tt[123][123];
inline int Read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x;
}
inline void Write(int x){
if(x>=10)Write(x/10);
putchar(x%10+48);
}
int main(){
s=Read(),n=Read(),m=Read();
register int i,j,k;
if(s==1){
for(i=1;i<=n;++i)aa[i]=Read()<<1|1;
for(i=1;i<=n;++i){
for(j=1;j<=m;++j){
if(j>=aa[i])dp[i][j]=max(dp[i-1][j],dp[i-1][j-aa[i]]+i);
else dp[i][j]=dp[i-1][j];
}
}
}
else{
for(i=1;i<=s;++i){
for(j=1;j<=n;++j)a[i][j]=Read()<<1|1,tt[j][i]=a[i][j];
}
for(i=1;i<=n;++i)sort(tt[i]+1,tt[i]+s+1);
for(i=1;i<=n;++i){
for(j=1;j<=m;++j){
for(k=1;k<=s;++k){
if(j>=tt[i][k])dp[i][j]=max(dp[i][j],max(dp[i-1][j],dp[i-1][j-tt[i][k]]+i*k));
else dp[i][j]=max(dp[i][j],dp[i-1][j]);
}
}
}
}
Write(dp[n][m]);
return 0;
}
祝贺自己成功地在考场上推出了比较复杂的状态转移方程,加油!\
T2 小X的二叉树
题目描述
小 X 研究的二叉树是一棵有
n
n
n 个点的
∆
k
∆k
∆k 树。
小 X 认为,没有点权的二叉树是没有灵魂的,于是这棵树第
i
i
i 个点有点权
a
i
a_i
ai 。
小 X 认为,一棵二叉树是
∆
k
∆k
∆k 树,当且仅当任意一个点的点权和它的所有祖先的点权的差的绝对值都不超过
k
k
k。显然地,如果一棵二叉树是
∆
k
∆k
∆k 树,那么它一定是
∆
(
k
+
1
)
∆(k+1)
∆(k+1) 树。
小 X 认为,一棵
∆
k
∆k
∆k 树按照中序遍历构成的序列一定有优美的性质。
然而趁小 X 想着这个二叉树睡着的时候,小 R 把他的树删了。小 X 醒后发现自己的树不见了,还好中序遍历得到的点权序列 p p p 还在。小 X 想让你帮他检查一下,这个序列是否可以通过一棵 ∆ k ∆k ∆k 树中序遍历得到。
输入格式
第一行一个整数
T
T
T ,表示数据组数。
每组数据包括:
第一行两个整数
n
,
k
n,k
n,k 。第二行共
n
n
n 个数,描述中序遍历的序列。
输出格式
共 T T T 行,分别是每组数据的询问结果, Y e s / N o Yes/No Yes/No 存在或不存在。
输入样例
3
6 10
2 7 15 8 9 5
6 8
2 7 15 8 9 5
6 7
2 7 15 8 9 5
输出样例
Yes
Yes
No
数据范围
对于
10
%
10\%
10% 的数据,
1
≤
n
≤
5
1\le n\le5
1≤n≤5 。
对于
30
%
30\%
30% 的数据,
1
≤
n
≤
200
1\le n\le 200
1≤n≤200。
对于
60
%
60\%
60% 的数据,
1
≤
n
≤
5
∗
1
0
3
1\le n\le 5*10^3
1≤n≤5∗103。
对于全部的数据,
1
≤
n
≤
2
∗
1
0
5
,
1
≤
Σ
n
≤
1
0
6
,
0
≤
p
i
,
k
≤
1
0
9
,
1
≤
T
≤
6
1\le n\le2*10^5,1\le\Sigma n\le10^6,0\le p_i,k\le10^9,1\le T\le6
1≤n≤2∗105,1≤Σn≤106,0≤pi,k≤109,1≤T≤6 。
解析
\
L v . 1 Lv.~1 Lv. 1
暴搜瞎搞, O ( u n k n o w n ) O(unknown) O(unknown) ,期望得分 10 \color{f02800}10 10 。
L v . 2 Lv.~2 Lv. 2
注意到题目中每个点对它祖先的限制,可以等价为一个点对它子树的限制,而中序遍历中每个子树是一个区间,所以可以区间 D P DP DP 。设 f ( l , r ) f(l,r) f(l,r) 表示区间 [ l , r ] [l,r] [l,r] 是否可行,转移时枚举当前区间表示子树的根 i i i ,需要满足 p i − k p_i−k pi−k 不超过区间 min \min min , p i + k p_i+k pi+k 不小于区间 max \max max 。时间复杂度 O ( n 3 ) O(n^3) O(n3) ,期望得分: 30 \color{f08b00}30 30 。
L v . 3 Lv.~3 Lv. 3
注意到如下结论:题目中给定的序列合法的充要条件是对于任意一个区间,都存在一个数使得这个数与区间其他数的差的绝对值不超过 k k k 。感性理解就是寻找区间是否存在满足条件的数,如果存在,就不用继续从其他合法的点往下搜了。如果到了左右端点重合的时候,这部分返回 1 1 1 。如果当前的点左边和右边都合法就是合法,继续向下,否则不合法。如果啥没有搜到返回 0 0 0 。
详细说明:
充分性:
若任意一个区间都满足此性质,那么我们一定可以构造出一棵满足题意的二叉树。我们从区间
[
1
,
n
]
[1,n]
[1,n] 开始构造,选择一个满足条件的
i
i
i ,将
i
i
i 作为当前子树的根,接着递归构造
[
l
,
i
−
1
]
[l,i−1]
[l,i−1] 和
[
i
+
1
,
r
]
[i+1,r]
[i+1,r] 。由于每个区间都存在一个数满足条件,所以一定有合法的
i
i
i,也就能构造出满足题意的树。
必要性:
如果这是由一棵合法的树中序遍历得到的点权序列,那么每个区间中深度最浅(最先抓出)的点一定是其他点的祖先,而这个点必须要满足条件,所以每个区间一定存在一个数,满足与区间内其他数的差的绝对值不超过
k
k
k 。于是我们就要判断原序列是否每个区间都存在一个数满足与其他数的差的绝对值不超过
k
k
k 。我们考虑分治,对于当前区间
[
l
,
r
]
[l,r]
[l,r] ,找到一个
i
(
l
≤
i
≤
r
)
i(l\le i\le r)
i(l≤i≤r) 使得
p
i
−
k
p_i−k
pi−k 不超过区间
min
\min
min ,
p
i
+
k
p_i+k
pi+k 不小于区间
max
\max
max ,这时跨过
i
i
i 的区间一定都合法,分治到
[
l
,
i
−
1
]
[l,i−1]
[l,i−1] 和
[
i
+
1
,
r
]
[i+1,r]
[i+1,r] 判断即可。
时间复杂度 T ( n ) = T ( k ) + T ( n − k − 1 ) + O ( k ) = O ( n 2 ) T(n)=T(k)+T(n−k−1)+O(k)=O(n^2) T(n)=T(k)+T(n−k−1)+O(k)=O(n2) ,期望得分: 60 \color{bef000}60 60 。
L v . 4 Lv.~4 Lv. 4
因为上一个级别是分开枚举的,因此时间复杂度次数是 2 2 2 。怎么优化呢?注意到位置不影响答案,可以改为同时从区间两端往中间扫,走一步同时判两个是否合法(第 t t t 步判断 l + t l+t l+t 和 r − t r-t r−t )。时间复杂度: T ( n ) = T ( k ) + T ( n − k − 1 ) + min { O ( k ) , O ( n − k ) } = O ( n log 2 n ) T(n)=T(k)+T(n−k−1)+\min\{O(k),O(n−k)\}=O(n\log_2n) T(n)=T(k)+T(n−k−1)+min{O(k),O(n−k)}=O(nlog2n) 。对于区间最小值可以用 S T ST ST 表线性对数预处理,查询 O ( 1 ) O(1) O(1) 。总的时间复杂度是 O ( n log 2 n ) O(n\log_2n) O(nlog2n) 。期望得分: 100 \color{00e600}100 100 。
code
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int n,k,stmax[233333][19],stmin[233333][19],a[233333];//在WOJ不能加inline或register小心RE
int QD(int ll,int rr){//查小
int kk=log2(rr-ll+1);
return max(stmax[ll][kk],stmax[rr-(1<<kk)+1][kk]);
}
int QX(int ll,int rr){//查大
int kk=log2(rr-ll+1);
return min(stmin[ll][kk],stmin[rr-(1<<kk)+1][kk]);
}
int fhltql(int l,int r){
if(l>=r)return 1;//到底了,不用拆分了
int minn=QX(l,r),maxx=QD(l,r);//求区间极大值极小值
for(int i=0;i+l<=r-i;i++){//两边向中枚举,不会在大区间耗时过多
if(a[l+i]-k<=minn&&a[l+i]+k>=maxx)//左指针满足deltaKtree
return fhltql(l,l+i-1)&&fhltql(l+i+1,r);//左边和右边依次递归,两边都可以才合法
if(a[r-i]-k<=minn&&a[r-i]+k>=maxx)//右指针满足deltaKtree
return fhltql(l,r-i-1)&&fhltql(r-i+1,r);//小F太强了
}
return 0;//啥都没有找到,宣告失败
}
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);
int i,j;//卡常数
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
stmax[i][0]=stmin[i][0]=a[i];//底层
}
for(j=1;j<=18;j++){
for(i=1;i+(1<<j)-1<=n;i++){
stmax[i][j]=max(stmax[i][j-1],stmax[i+(1<<(j-1))][j-1]);//建大值斯特表
stmin[i][j]=min(stmin[i][j-1],stmin[i+(1<<(j-1))][j-1]);//建小值斯特表
}
}
if(fhltql(1,n))puts("Yes");//总区间存在
else puts("No");//不存在
}
return 0;//完结撒花
}