2020.02.10日常总结——CF趣题讲解

7 篇文章 0 订阅
2 篇文章 0 订阅

Boxes   Game \color{green}{\text{Boxes\ \ \ Game}} Boxes   Game

【 原 题 链 接 】 : \color{blue}{【原题链接】:} 点此进入原题面(英文题面)

【 题 目 大 意 】 : \color{blue}{【题目大意】:} AB在玩游戏。他们面前有一个数组。他们只能拿最左边或最右边的数,然后加进他们的得分,得分越大越好。他们都会以最优的方法拿,求最后的分数差。(感谢rxz老师的翻译)

【 思 路 】 : \color{blue}{【思路】:} A l , r A_{l,r} Al,r表示先手在区间 [ l , r ] [l,r] [l,r]内的得分, B l , r B_{l,r} Bl,r表示后手在区间 [ l , r ] [l,r] [l,r]内的得分, W i W_i Wi表示第 i i i个数, s u m l , r sum_{l,r} suml,r表示区间 [ l , r ] [l,r] [l,r]内数的总和。所以,我们有:

A l , r = max ⁡ { W l + B l + 1 , r , W r + B l , r − 1 } A_{l,r}=\max \{W_l+B_{l+1,r},W_r+B_{l,r-1} \} Al,r=max{Wl+Bl+1,r,Wr+Bl,r1}

B l , r = s u m l , r − A l , r B_{l,r}=sum_{l,r}-A_{l,r} Bl,r=suml,rAl,r

我来解释一下它们的含义:第一条转移式比较难理解,为什么前面是 A A A而后面就成了 B B B了呢?不是同一个人吗?其实不然, A A A B B B的定义并不是针对某个人的,仅仅是先手后手。什么意思?就是任何一个人都可以调用 A A A B B B来求出自己的得分。

那第一条转移式是什么意思呢?就是说, W l + B l + 1 , r W_l+B_{l+1,r} Wl+Bl+1,r即先手取了第 l l l个数,然后A和·B就只能在区间 [ l + 1 , r ] [l+1,r] [l+1,r]中取数,而因为轮到其它人选了,所以在区间 [ l + 1 , r ] [l+1,r] [l+1,r]中他是后手,所以是 B l + 1 , r B_{l+1,r} Bl+1,r W r + B l , r − 1 W_r+B_{l,r-1} Wr+Bl,r1类似。

B B B的转移比较简单,因为后手没有什么自动权,所以他的得分就是区间得分总和 − - 先手的得分。

于是,我们用一个递归 + + +两函数互相调用就可以求出答案。答案即 A 1 , n − B 1 , n A_{1,n}-B_{1,n} A1,nB1,n

【 代 码 】 : \color{blue}{【代码】:}

int a[1010][1010],test_number,n;
int b[1010][1010],pre[1010],w[1010];
bool va[1010][1010],vb[1010][1010];
inline void init_the_array(){
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	memset(w,0,sizeof(w));
	memset(pre,0,sizeof(pre));
	memset(va,false,sizeof(va));
	memset(vb,false,sizeof(vb));
}//初始化数组 
inline void read_the_data(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&w[i]);
		pre[i]=pre[i-1]+w[i];
	}
}//读入数据以求解 
inline int sum(int l,int r){
	return pre[r]-pre[l-1];
}//利用前缀和O(1)求[l,r]的分数和 
inline int A(int l,int r);
inline int B(int l,int r);
inline int A(int l,int r){
	if (l==r) return w[l];
	if (va[l][r]) return a[l][r];
	a[l][r]=max(w[l]+B(l+1,r),w[r]+B(l,r-1));
	va[l][r]=true;return a[l][r];
}//计算先手在区间[l,r]内的得分
inline int B(int l,int r){
	if (vb[l][r]) return b[l][r];
	b[l][r]=sum(l,r)-A(l,r);
	vb[l][r]=true;return b[l][r];
}//计算后手在区间[l,r]内的得分
inline void calc_the_answer(){
	init_the_array();
	read_the_data();
	printf("%d\n",A(1,n)-B(1,n));
} 
int main(){
	scanf("%d",&test_number);
	while (test_number--)
		calc_the_answer();
	return 0;
}


*****************************
语言:C++
状态:Accepted
得分:100分
备注:无头文件和快读read()函数
*****************************

Ahmad   and   Spells \color{green}{\text{Ahmad\ \ \ and\ \ \ Spells}} Ahmad   and   Spells

【 原 题 链 接 】 : \color{blue}{【原题链接】:} 点此进入原题面(英文题面)

【 题 目 大 意 】 : \color{blue}{【题目大意】:} 我们有一个剑客,需要给对手 n n n点伤害。他每 x x x秒可以给 1 1 1点伤害。有 m m m种技能,需要 b i b_i bi个金币,可把间隔 x x x改为 a i a_i ai (他最多只能学 1 1 1种技能)。

除此之外,现场有 k k k个群众,他需要 d i d_i di个金币,可以给出 c i c_i ci点伤害(剑客只能雇 1 1 1个群众)。

求剑客最少要多少时间杀死对手。

【 思 路 】 : \color{blue}{【思路】:} 若有两个群众ab,若a需要的金币比b少,且a的伤害比b多,那么我们把b删去(因为a比他更物美价廉),剩下的群众满足需要金币越多,伤害越大。

枚举学哪个技能,剩下的钱记为 x x x。二分群众中需要的金币小于等于 x x x且最大的群众(他在可以雇佣的前提下,伤害最大),我们在学习一次技能的前提下,雇佣他。

于是,我们可以枚举我们学习哪个技能,然后通过二分计算我们可以雇佣哪个群众,然后计算去最优值。

注意,可以不学技能,这样可能也最优(毕竟没花钱)!!!当然,也可以不选群众。

题目就做完了……

【 代 码 】 : \color{blue}{【代码】:}

typedef long long ll;
const int N=1e5+100;
struct hero_id{
	ll c,d;
	bool operator < (hero_id p) const{
		return d<p.d;
	}
	hero_id(ll _c=0,ll _d=0){c=_c;d=_d;}
}h[N];ll a[N],b[N];
vector<hero_id> chose;
ll n,m,k,x,s,ans;
void read_the_data(){
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	memset(h,0,sizeof(h));
	chose.clear();//int i;
	n=read();m=read();k=read();
	x=read();s=read();
	for(int i=1;i<=m;i++)
		a[i]=read();
	for(int i=1;i<=m;i++)
		b[i]=read();
	for(int i=1;i<=k;i++)
		h[i].c=read();
	for(int i=1;i<=k;i++)
		h[i].d=read();
}
void choose_heros(){
	sort(h+1,h+k+1);
	chose.push_back(hero_id(0,0));
	register ll maxn=0ll;
	for(int i=1;i<=k;i++){
		if (h[i].c<=maxn) continue;
		chose.push_back(h[i]);maxn=h[i].c;
	}
}
inline ll Left(ll p){
	return (*(--upper_bound(chose.begin(),chose.end(),hero_id(0,p)))).c;
}
inline void calc_answer(){
	ans=9e18;a[0]=x;b[0]=0;
	for(int i=0;i<=m;i++)
		if (s>=b[i]){
			ll used=Left(s-b[i]),cost;
			if (n-used<=0) cost=0ll;
			else cost=(n-used)*a[i];
			ans=min(ans,cost);
		}
}
int test_number;
int main(){
	test_number=read();
	while (test_number--){
		read_the_data();
		choose_heros();
		calc_answer();
		printf("%lld\n",ans);
	}
	return 0;
}

*****************************
语言:C++
状态:Accepted
得分:100分
备注:无头文件和快读read()函数
*****************************
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值