牛客挑战赛74(A,B,C,D)

比赛链接

这场纯纯shit,C是大讨论,D是大模拟。


A 硫酸钡之梦

思路:

发现我们到达第 i i i 个位置的时候,状态其实只有 3 3 3 个,取了的个数-未取的个数=-1,0或1。而前面的选取方式不会影响到后面的选取(无后效性),所以考虑动态规划。

d p [ i ] [ s t ] dp[i][st] dp[i][st] 表示前 i i i 个糖果,状态为 s t st st (取了-未取+1=0,1或2)的最大值。状态转移方程也比较好推:
{ d p [ i ] [ 0 ] = d p [ i − 1 ] [ 1 ] d p [ i ] [ 1 ] = m a x { d p [ i − 1 ] [ 2 ] , d p [ i − 1 ] [ 0 ] + a i } d p [ i ] [ 2 ] = d p [ i − 1 ] [ 1 ] + a i \left\{\begin{aligned} dp[i][0] & = dp[i-1][1] \\ dp[i][1] & = max\{ dp[i-1][2],dp[i-1][0]+a_i\} \\ dp[i][2] & = dp[i-1][1]+a_i \end{aligned}\right. dp[i][0]dp[i][1]dp[i][2]=dp[i1][1]=max{dp[i1][2],dp[i1][0]+ai}=dp[i1][1]+ai

code:

还能滚动数组优化

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1e5+5;
typedef long long ll;

int n;
ll a[maxn];
ll dp[maxn][3];//前i个糖果,取了-未取+1=j 

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	dp[0][0]=dp[0][2]=-1e18;
	for(int i=1;i<=n;i++){
		dp[i][0]=dp[i-1][1];
		dp[i][1]=max(dp[i-1][2],dp[i-1][0]+a[i]);
		dp[i][2]=dp[i-1][1]+a[i];
	}
	cout<<max(dp[n][0],max(dp[n][1],dp[n][2]));
	return 0;
}

B 伐木机不要石头!!!(easy version)

思路:

暴露了出题人是个粥p的事实

比较简单的贪心,我们用破坏力尽可能小的斧子先砍它能砍的最硬的树,依次类推。这个用两个set就能做。

不过破坏力最小的斧子它能砍的最硬的树后面的所有斧子其实也都能砍,所以让这个斧子砍硬的树,让其他斧子砍最软的树的方案我们可以替换成这个斧子砍最软的树,其他斧子砍硬树。

所以我们可以小斧子直接去砍最软的树,能砍就砍,砍不了那么说明这个斧子就没用了,直接丢掉就行了。

code:

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;

int n,m;
int ans=0;
priority_queue<int,vector<int>,greater<int> > a,b;

int main(){
	cin>>n>>m;
	for(int i=1,t;i<=n;i++){
		cin>>t;
		a.push(t);
	}
	for(int i=1,t;i<=m;i++){
		cin>>t;
		b.push(t);
	}
	while(!a.empty() && !b.empty()){
		int ta=a.top(),tb=b.top();
		if(ta<=tb){
			a.pop();
			b.pop();
			ans++;
		}
		else {
			b.pop();
		}
	}
	cout<<ans<<endl;
	return 0;
}

C 写作业

思路:

一眼顶针,鉴定为纯纯的挠滩题目。

因为就两个科目两个人,每个情况也没有什么特别的共性,所以尝试直接讨论所有情况。

当然不可能直接硬讨论,每个人的每个科目都有两种状态:写或者抄。硬讨论的话顺序,完成时间都是个问题,情况数量爆炸。所以这里首先提出两个性质进行优化:

  1. 某个科目一定有人写,不能两个人都抄。
  2. 如果一个人既有写的任务也有抄的任务,那么他一定先进行写的任务。

第一条显然。第二条其实也比较显然,如果我们先写再抄的话,抄的时间有可能延后,除此之外就没什么影响了,但是如果先抄再写的话,抄要赶紧,写摆在后面,花的时间一定更多。

先不考虑某个人写作业的顺序,假设两个人分别叫 a , b a,b a,b,科目分别叫 1 , 2 1,2 1,2,可以分为 2 4 − 2 2 − 2 2 + 1 = 9 2^4-2^2-2^2+1=9 242222+1=9 种情况(总情况 2 4 2^4 24 个,科目 1 1 1 两人都抄的情况数 2 2 2^2 22 个,同理科目 2 2 2,减多了再加回科目 1 , 2 1,2 1,2 两人都抄的情况数 1 1 1 个):

  1. a a a 1 1 1 b b b 2 2 2,然后互相抄
  2. a a a 2 2 2 b b b 1 1 1,然后互相抄
  3. a a a 1 , 2 1,2 1,2 b b b
  4. a a a 抄, b b b 1 , 2 1,2 1,2
  5. a a a 1 , 2 1,2 1,2 b b b 1 1 1
  6. a a a 1 , 2 1,2 1,2 b b b 2 2 2
  7. a a a 1 1 1 b b b 1 , 2 1,2 1,2
  8. a a a 2 2 2 b b b 1 , 2 1,2 1,2
  9. a a a 1 , 2 1,2 1,2 b b b 1 , 2 1,2 1,2

一个人如果写两个科目,另一个人抄的话,还需要讨论写的顺序,所以严格来说其实是 15 15 15 种情况。

code:

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

ll T,a1,a2,b1,b2;

int main(){
	cin>>T;
	while(T--){
		cin>>a1>>a2>>b1>>b2;
		ll ans=1e18,t;
		
		//1
		t=max(a1,b2)+max(a2/2,b1/2);
		ans=min(ans,t);
		
		//2
		t=max(a2,b1)+max(a1/2,b2/2);
		ans=min(ans,t);
		
		//3
		t=a1+max(a2,b1/2)+b2/2;
		ans=min(ans,t);
		t=a2+max(a1,b2/2)+b1/2;
		ans=min(ans,t);
		
		//4
		t=b1+max(b2,a1/2)+a2/2;
		ans=min(ans,t);
		t=b2+max(b1,a2/2)+a1/2;
		ans=min(ans,t);
		
		//5
		t=max(a1+a2,b1)+b2/2;
		ans=min(ans,t);
		t=max(a2+a1,max(a2,b1)+b2/2);
		ans=min(ans,t);
		
		//6
		t=max(a2+a1,b2)+b1/2;
		ans=min(ans,t);
		t=max(a1+a2,max(a1,b2)+b1/2);
		ans=min(ans,t);
		
		//7
		t=max(b1+b2,a1)+a2/2;
		ans=min(ans,t);
		t=max(b2+b1,max(b2,a1)+a2/2);
		ans=min(ans,t);
		
		//8
		t=max(b2+b1,a2)+a1/2;
		ans=min(ans,t);
		t=max(b1+b2,max(b1,a2)+a1/2);
		ans=min(ans,t);
		
		//9
		t=max(a1+a2,b1+b2);
		ans=min(ans,t);
		
		cout<<ans<<endl;
	}
	return 0;
}

D 妈妈,世界是一个巨大的脑叶公司

思路:

大模拟,屎。大模拟带优化,屎中屎。

朴素的模拟想法其实很简单,用个容器把所有在场的员工信息存起来,尤其是实时的血量。对容器内的员工进行操作,每次操作后结算回合的伤害(所有员工的攻击力)。

但是操作 3 3 3 需要给全体员工的生命减掉一个值,并删去死亡的员工。如果我们真的对每个员工进行操作,会 TLE,因此考虑优化。

我们可以把怪的攻击存起来,如果总的攻击大于等于了员工血量,我们就删掉这个员工。为了快速查询血量最小的员工,我们用map存储员工信息,并按血量进行排序,同时其他操作还需要查询员工的编号,因此用双map来存。一个map存储编号到员工信息的映射,另一个存储员工信息到编号的映射,这样就可以编号和员工信息双向查询了。

但是有的员工加入容器后,之前的攻击对这个员工就不算数了,但是我们又不能为了这一次操作去把怪的攻击更新到其他员工身上,会T。所以我们仍然让新加的员工受到攻击,但是给这个员工一点补偿——设置一个护盾值。

治疗超过生命上限的部分不生效,但是我们根据前面存储的每个员工的血量,带护盾值的等效血量,怪物的攻击可以计算出员工当前剩余的生命值和护盾。然后治疗量和能接受的治疗量取较小值加到生命值上即可。也可以维护住。

这样其实就可以写了,实际写的时候可以把一些需要求和的数值动态进行维护,比如阵亡的员工个数,在场的员工总攻击力等。

code:

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;

ll n,m,k;
ll a[maxn],b[maxn];
struct opter{
	ll hp,shl;//当前实时血量,等效血量
	ll idx;
	bool operator<(const opter x)const{
		if(shl!=x.shl)return shl<x.shl;
		return idx<x.idx;
		//这里按编号排序是必要的
		//不写的话没mp2会把血量相同的opt看成一个,然后去重 
	};
};

map<ll,opter> mp1;//编号到信息
map<opter,ll> mp2;//信息到编号 

void rm(ll idx){
	opter t=mp1[idx];
	mp1.erase(idx);
	mp2.erase(t);
}
ll atk=0,tot=0;//怪的累计伤害,我方单次伤害 
ll dead=0;//死亡人数 

int main(){
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)cin>>b[i];
	for(int i=1;i<=n;i++){
		opter t={b[i],b[i],i};
		tot+=a[i];
		mp1[i]=t;
		mp2[t]=i;
	}
	
	while(k-- && m>0 && dead!=n){
		int operat;
		cin>>operat;
		if(operat==1){
			ll x;
			cin>>x;
			opter t={b[x],b[x]+atk,x};
			mp1[x]=t;
			mp2[t]=x;
			tot+=a[x];
		}
		else if(operat==2){
			ll x;
			cin>>x;
			rm(x);
			tot-=a[x];
		}
		else if(operat==3){
			ll y;
			cin>>y;
			atk+=y;
			while(!mp2.empty() && atk>=mp2.begin()->first.shl){
				ll idx=mp2.begin()->first.idx;
				tot-=a[idx];
				dead++;
				rm(idx);
			}
		}
		else {
			ll x,h;
			cin>>x>>h;
			if(mp1.find(x)!=mp1.end()){
				opter t=mp1[x];
				
				ll cur,shell;//当前血量 护盾值 
				if(t.shl-atk>=t.hp){
					cur=t.hp;
					shell=t.shl-atk-t.hp;
				}
				else {
					cur=t.shl-atk;
					shell=0;
				}
				
				h=min(h,b[x]-cur);//治疗量
				cur+=h; 
				
				if(h>0){
					rm(x);
					t={cur,cur+shell+atk,x};
					mp1[x]=t;
					mp2[t]=x;
				} 
			}
		}
		
//		cout<<endl;
//		printf("atk=%d tot=%d dead=%d monsterhp=%d\n",atk,tot,dead,m);
//		for(auto [idx,opt]:mp1){
//			auto [hp,shl,id]=opt;
//			printf("hp:%d shl:%d id:%d\n",hp,shl,id);
//		}
//		cout<<"---------------------------------------------\n";
//		for(auto [opt,idx]:mp2){
//			auto [hp,shl,id]=opt;
//			printf("hp:%d shl:%d id:%d\n",hp,shl,id);
//		}
//		cout<<endl;

		m-=tot;
	}
	if(m<=0){
		cout<<"YES\n"<<n-dead<<endl;
	}
	else {
		cout<<"NO\n";
	}
	return 0;
}
  • 28
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值