[AHOI2017/HNOI2017] 大佬

传送门

本题使用的算法均是 dp,bfs 等最基础的算法,主要的难点在于对于各个步骤的拆分和处理,很值得一做。

题意概要

\(m\) 个询问,第 i 个询问大佬的自信值为 \(C_i\)。你初始自信值为 \(mc\),等级 \(L=0\),嘲讽值 \(F=1\)。对于每一个询问,你都有 n 天时间。在第 i 天你的的自信值会下降 \(a_i\),如果你的自信值为负数,那么你就失败了。在第 i 天,你可以选择下列一个操作:

  1. 使得大佬自信值下降 1
  2. 使得自己的自信值增加 \(w_i\)
  3. 把自己的等级 +1
  4. 把自己的 \(F\) 乘上 \(L\)
  5. 使得大佬的自信值下降 \(F\),之后 \(L=0,F=1\)

操作 5 最多进行两次,若最后大佬自信值恰好为 0 ,则你成功,否则你失败,对于每个询问判断能否成功。

暴力(40 pts)

注意到状态维数很多,若采用 dp 实现,所需空间过大。很自然地想到使用 dfs。

dfs 代码
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m,mc,a[N],w[N];
bool dfs(int now,int me,int dl,int L,int F,int ci){
	if(dl==0)return true;
	if(me-a[now]<0)return false;
	if(now==n+1&&dl>0)return false;
	me-=a[now];
	int f=0;
	if(dl>=1)f|=dfs(now+1,me,dl-1,L,F,ci);
	f|=dfs(now+1,min(mc,me+w[now]),dl,L,F,ci);
	f|=dfs(now+1,me,dl,L+1,F,ci);
	f|=dfs(now+1,me,dl,L,F*L,ci);
	if(ci<2&&dl>=F)f|=dfs(now+1,me,dl-F,0,1,ci+1);
	return f;
}
int main(){
	scanf("%d%d%d",&n,&m,&mc);
	for(int i=1;i<=n;++i)scanf("%d",&a[i]);
	for(int i=1;i<=n;++i)scanf("%d",&w[i]);
	for(int i=1,c;i<=m;++i){
		scanf("%d",&c);
		printf("%d\n",dfs(1,mc,c,0,1,0));
	}
}

正解

我们这里难以采用其他高效算法而只能使用 dfs 的主要原因是在处理过程中需要维护的状态维度太多了。正常的题目这里可能是各种奇妙剪枝,但这题提供了一个新的思路就是把各个维度拿出来单独考虑。

首先要注意到,这题其实可以看作两个子问题:

  1. 让自己活下来
  2. 让大佬自信值为0

其次,有个贪心思路是我们进行的 2 操作越少越好,因为就算我们最后剩下一些天数没用,也可以通过刷水题度过。
那么子问题一就变成了为了让自己活下来所需最少的 2 操作次数,可以用一个简单的 dp 求出来。

dp 代码
//dp[i][j]表示处理前i次大佬嘲讽,当前自信值为j时最多剩下几天进行
for(int i=1; i<=n; i++)
	for(int j=a[i]; j<=mc; j++) {
		dp[i][j-a[i]]=max(dp[i][j-a[i]],dp[i-1][j]+1);
		int temp=min(mc,j-a[i]+w[i]);
		dp[i][temp]=max(dp[i][temp],dp[i-1][j]);
	}
for(int i=1; i<=n; i++)
	for(int j=1; j<=mc; j++)
		day=max(day,dp[i][j]);

子问题一解决后,我们发现剩下的维度仍然很多,还是没法用高效的算法解决,所以我们的思路是能不能再拆出一些维度单独处理。

注意到操作 2,3,4 可以看作是第一类操作,而操作 1 可以看作第二类,同时有还有一个关键信息 操作 5 只能进行两次。那么我们又可以转化一下问题,把第二类作为第一类操作的补充,这里第一类操作显然复杂一点,我们考虑怎么处理。

对于这个子问题,我们注意到有用的信息是天数,\(L\)\(F\),可能有些同学又往 dfs 上想了,但是别忘了我们刚刚那个贪心就算我们最后剩下一些天数没用,也可以刷水题度过。所以这里的想法就是我们用越少的天数获得我们需要的嘲讽值,结果一定越优,这显然是一个 bfs,但是直接写复杂度还是不够优秀,这里引入一个小剪枝:对于嘲讽值大于大佬自信值得情况是不用考虑的。实现的时候注意去重以及对于相同嘲讽值取最小天数。

bfs 代码
q.push((p) {0,1,0});
while(q.size()) {
	p x=q.front();
	q.pop();
	if(x.d==day-1)continue;
	if(x.l==0)q.push((p) {x.l+1,x.f,x.d+1});
	else {
		int res=x.l*x.f;
		if(x.f*x.l<=1e8&&s.find(make_pair(res,x.d+1))==s.end()) {
			s.insert(make_pair(res,x.d+1));
			if(mp.find(res)==mp.end())mp[res]=++num,h[num].first=res,h[num].second=x.d+1;
			q.push((p) {x.l+1,x.f,x.d+1});
			q.push((p) {x.l,res,x.d+1});
		}
	}
}

终于到了最后一步了,这里就很简单了,我们分别处理嘲讽零次,一次,两次的情况即可。

check 代码
inline bool check() {
	if(c<=day)return 1;
	for(int i=1; i<=num; i++) {
		if(h[i].first>c)break;
		if(h[i].first+day-1-h[i].second>=c)return 1;
	}
	int i=1,j=num;
	for(int i=1; i<=num; i++) {
		if(h[i].first>c)break;
		while(h[i].first+h[j].first>c)j--;
		int p=c-(day-2)-(h[i].first-h[i].second);
		if(ma[j]>=p)return 1;
	}
	return 0;
}

Code

#include <bits/stdc++.h>
#define int long long
using namespace std;
#define N 110
int n,m,mc,c,day;
int a[N],w[N];
int dp[N][N];
void getday() {
	for(int i=1; i<=n; i++)
		for(int j=a[i]; j<=mc; j++) {
			dp[i][j-a[i]]=max(dp[i][j-a[i]],dp[i-1][j]+1);
			int temp=min(mc,j-a[i]+w[i]);
			dp[i][temp]=max(dp[i][temp],dp[i-1][j]);
		}
	for(int i=1; i<=n; i++)
		for(int j=1; j<=mc; j++)
			day=max(day,dp[i][j]);
}
struct p {
	int l,f,d;
};
queue <p> q;
set <pair<int,int> >s;
pair <int,int> h[4000050];
int num;
int ma[4000050];
map <int,int> mp;
inline void bfs() {
	q.push((p) {
		0,1,0
	});
	while(q.size()) {
		p x=q.front();
		q.pop();
		if(x.d==day-1)continue;
		if(x.l==0)q.push((p) {
			x.l+1,x.f,x.d+1
		});
		else {
			int res=x.l*x.f;
			if(x.f*x.l<=1e8&&s.find(make_pair(res,x.d+1))==s.end()) {
				s.insert(make_pair(res,x.d+1));
				if(mp.find(res)==mp.end())mp[res]=++num,h[num].first=res,h[num].second=x.d+1;
				q.push((p) {
					x.l+1,x.f,x.d+1
				});
				q.push((p) {
					x.l,res,x.d+1
				});
			}
		}
	}
	sort(h+1,h+num+1);
	for(int i=1; i<=num; i++)ma[i]=max(ma[i-1],h[i].first-h[i].second);
}
inline bool check() {
	if(c<=day)return 1;
	for(int i=1; i<=num; i++) {
		if(h[i].first>c)break;
		if(h[i].first+day-1-h[i].second>=c)return 1;
	}
	int i=1,j=num;
	for(int i=1; i<=num; i++) {
		if(h[i].first>c)break;
		while(h[i].first+h[j].first>c)j--;
		int p=c-(day-2)-(h[i].first-h[i].second);
		if(ma[j]>=p)return 1;
	}
	return 0;
}
signed main() {
	scanf("%lld%lld%lld",&n,&m,&mc);
	for(int i=1; i<=n; ++i)scanf("%lld",&a[i]);
	for(int i=1; i<=n; ++i)scanf("%lld",&w[i]);
	getday();
	bfs();
	while(m--) {
		scanf("%lld",&c);
		check()?puts("1"):puts("0");
	}
	return 0;
}
posted @ 2025-07-26 15:35  黑昼白夜  阅读(9)  评论(0)    收藏  举报