191109-T1~T2

T1 排兵布阵

题目描述

在游戏中有 nnn 座城堡,每局对战由两名玩家来争夺这些城堡。每名玩家有 mmm 名士兵,可以向第 iii 座城堡派遣 aia_iai 名士兵去争夺这个城堡,使得总士兵数不超过 mmm 。如果一名玩家向第 iii 座城堡派遣的士兵数严格大于对手派遣士兵数的 222 倍,那么这名玩家就占领了这座城堡,获得 iii 分。

现在你即将和其他 sss 名玩家两两对战,这 sss 场对决的派遣士兵方案必须相同。小 F 通过某些途径得知了其他 sss 名玩家即将使用的策略并告诉了你,你应该使用某种策略策略来最大化总分。

由于方案可能不唯一,你只需要输出你能获得的总分的最大值。

输入格式

输入第一行包含三个正整数 s,n,ms,n,ms,n,m ,分别表示除了小 X 以外的玩家人数、城堡数
和每名玩家拥有的士兵数。

接下来 sss 行,每行 nnn 个非负整数,表示一名玩家的策略,其中第 iii 个数 aia_iai 表示这
名玩家向第 iii 座城堡派遣的士兵数。

输出格式

输出一行一个非负整数,表示你能获得的最大得分

输入样例

2 3 6
2 2 10
0 0 0

输出样例

8

一种最佳策略为向第 111 座城堡派遣 222 名士兵,向第 222 座城堡派遣 555 名士兵,向第 333 座城堡派遣 111 名士兵。

数据范围

对于 10%10\%10% 的数据,保证 s=1,n≤3,m≤10s=1,n\le3,m\le10s=1,n3,m10
对于 20%20\%20% 的数据,保证 s=1,n≤10,m≤100s=1,n≤10,m\le100s=1,n10,m100
对于 40% 的数据,保证 n≤10,m≤100n\le10,m\le100n10,m100
对于另外 20%20\%20% 的数据,保证 s=1s=1s=1
对于 100%100\%100% 的数据,保证
1≤s≤1001\le s \le1001s1001≤n≤1001\le n \le 1001n100 1≤m≤2∗1041\le m \le2*10^41m2104
对于每名玩家,ai≥0,∑i=1nai≤ma_i\ge0,\sum_{i=1}^{n}a_i\le mai0,i=1naim
注意常数问题

解析

显然,最优的策略是在一座城市不派遣士兵或刚好派可以拿下这个城的士兵 (x<<1∣1x<<1|1x<<11) 选一个。接下来就是背包问题的一点变化(背包容积:士兵总数;物品重量:选前 kkk 个所有即选到的最大的那一个;物品价值:点的编号 i∗ki*kik(这一列击杀 kkk 人) )。DP方程见参考代码。时间复杂度 O(nms)O(nms)O(nms) ,在全部取最大的时候可能会有点卡,所以合理地加 inlineinlineinlineregisterregisterregister , 开启读入优化是必需的。常数减小,可以安全通过。

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 研究的二叉树是一棵有 nnn 个点的 ∆k∆kk 树。
小 X 认为,没有点权的二叉树是没有灵魂的,于是这棵树第 iii 个点有点权 aia_iai
小 X 认为,一棵二叉树是 ∆k∆kk 树,当且仅当任意一个点的点权和它的所有祖先的点权的差的绝对值都不超过 kkk。显然地,如果一棵二叉树是 ∆k∆kk 树,那么它一定是 ∆(k+1)∆(k+1)(k+1) 树。
小 X 认为,一棵 ∆k∆kk 树按照中序遍历构成的序列一定有优美的性质。

然而趁小 X 想着这个二叉树睡着的时候,小 R 把他的树删了。小 X 醒后发现自己的树不见了,还好中序遍历得到的点权序列 ppp 还在。小 X 想让你帮他检查一下,这个序列是否可以通过一棵 ∆k∆kk中序遍历得到。

输入格式

第一行一个整数 TTT ,表示数据组数。
每组数据包括:
第一行两个整数 n,kn,kn,k 。第二行共 nnn 个数,描述中序遍历的序列。

输出格式

TTT 行,分别是每组数据的询问结果,Yes/NoYes/NoYes/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≤51\le n\le51n5
对于 30%30\%30% 的数据, 1≤n≤2001\le n\le 2001n200
对于 60%60\%60% 的数据, 1≤n≤5∗1031\le n\le 5*10^31n5103
对于全部的数据, 1≤n≤2∗105,1≤Σn≤106,0≤pi,k≤109,1≤T≤61\le n\le2*10^5,1\le\Sigma n\le10^6,0\le p_i,k\le10^9,1\le T\le61n2105,1Σn106,0pi,k109,1T6

解析

\

Lv. 1Lv.~1Lv. 1

暴搜瞎搞, O(unknown)O(unknown)O(unknown) ,期望得分 10\color{f02800}1010

Lv. 2Lv.~2Lv. 2

注意到题目中每个点对它祖先的限制,可以等价为一个点对它子树的限制,而中序遍历中每个子树是一个区间,所以可以区间 DPDPDP 。设 f(l,r)f(l,r)f(l,r) 表示区间 [l,r][l,r][l,r] 是否可行,转移时枚举当前区间表示子树的根 iii ,需要满足 pi−kp_i−kpik 不超过区间 min⁡\minminpi+kp_i+kpi+k 不小于区间 max⁡\maxmax 。时间复杂度 O(n3)O(n^3)O(n3) ,期望得分:30\color{f08b00}3030

Lv. 3Lv.~3Lv. 3

注意到如下结论:题目中给定的序列合法的充要条件是对于任意一个区间,都存在一个数使得这个数与区间其他数的差的绝对值不超过 kkk 。感性理解就是寻找区间是否存在满足条件的数,如果存在,就不用继续从其他合法的点往下搜了。如果到了左右端点重合的时候,这部分返回 111 。如果当前的点左边和右边都合法就是合法,继续向下,否则不合法。如果啥没有搜到返回 000

详细说明:

充分性:
若任意一个区间都满足此性质,那么我们一定可以构造出一棵满足题意的二叉树。我们从区间 [1,n][1,n][1,n] 开始构造,选择一个满足条件的 iii ,将 iii 作为当前子树的根,接着递归构造 [l,i−1][l,i−1][l,i1][i+1,r][i+1,r][i+1,r] 。由于每个区间都存在一个数满足条件,所以一定有合法的 iii,也就能构造出满足题意的树。

必要性:
如果这是由一棵合法的树中序遍历得到的点权序列,那么每个区间中深度最浅(最先抓出)的点一定是其他点的祖先,而这个点必须要满足条件,所以每个区间一定存在一个数,满足与区间内其他数的差的绝对值不超过 kkk 。于是我们就要判断原序列是否每个区间都存在一个数满足与其他数的差的绝对值不超过 kkk 。我们考虑分治,对于当前区间 [l,r][l,r][l,r] ,找到一个 i(l≤i≤r)i(l\le i\le r)i(lir) 使得 pi−kp_i−kpik 不超过区间 min⁡\minminpi+kp_i+kpi+k 不小于区间 max⁡\maxmax ,这时跨过 iii 的区间一定都合法,分治到 [l,i−1][l,i−1][l,i1][i+1,r][i+1,r][i+1,r] 判断即可。

时间复杂度 T(n)=T(k)+T(n−k−1)+O(k)=O(n2)T(n)=T(k)+T(n−k−1)+O(k)=O(n^2)T(n)=T(k)+T(nk1)+O(k)=O(n2) ,期望得分:60\color{bef000}6060

Lv. 4Lv.~4Lv. 4

因为上一个级别是分开枚举的,因此时间复杂度次数是 222 。怎么优化呢?注意到位置不影响答案,可以改为同时从区间两端往中间扫,走一步同时判两个是否合法(第 ttt 步判断 l+tl+tl+tr−tr-trt )。时间复杂度:T(n)=T(k)+T(n−k−1)+min⁡{O(k),O(n−k)}=O(nlog⁡2n)T(n)=T(k)+T(n−k−1)+\min\{O(k),O(n−k)\}=O(n\log_2n)T(n)=T(k)+T(nk1)+min{O(k),O(nk)}=O(nlog2n) 。对于区间最小值可以用 STSTST 表线性对数预处理,查询 O(1)O(1)O(1) 。总的时间复杂度是 O(nlog⁡2n)O(n\log_2n)O(nlog2n) 。期望得分: 100\color{00e600}100100

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;//完结撒花
}

预祝CSP取得理想成绩!\color{f0f000}预祝CSP取得理想成绩!CSP

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值