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

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

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

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

\color{blue}{【思路】:} A l , r A_{l,r} 表示先手在区间 [ l , r ] [l,r] 内的得分, B l , r B_{l,r} 表示后手在区间 [ l , r ] [l,r] 内的得分, W i W_i 表示第 i i 个数, s u m l , r sum_{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} \}

B l , r = s u m l , r A l , r B_{l,r}=sum_{l,r}-A_{l,r}

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

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

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

于是,我们用一个递归 + + 两函数互相调用就可以求出答案。答案即 A 1 , n B 1 , n A_{1,n}-B_{1,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}}

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

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

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

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

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

枚举学哪个技能,剩下的钱记为 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()函数
*****************************
发布了103 篇原创文章 · 获赞 4 · 访问量 6729

猜你喜欢

转载自blog.csdn.net/ZHUYINGYE_123456/article/details/104247422