[AHOI2017/HNOI2017] 大佬
本题使用的算法均是 dp,bfs 等最基础的算法,主要的难点在于对于各个步骤的拆分和处理,很值得一做。
题意概要
有 \(m\) 个询问,第 i 个询问大佬的自信值为 \(C_i\)。你初始自信值为 \(mc\),等级 \(L=0\),嘲讽值 \(F=1\)。对于每一个询问,你都有 n 天时间。在第 i 天你的的自信值会下降 \(a_i\),如果你的自信值为负数,那么你就失败了。在第 i 天,你可以选择下列一个操作:
- 使得大佬自信值下降 1
- 使得自己的自信值增加 \(w_i\)
- 把自己的等级 +1
- 把自己的 \(F\) 乘上 \(L\)
- 使得大佬的自信值下降 \(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 的主要原因是在处理过程中需要维护的状态维度太多了。正常的题目这里可能是各种奇妙剪枝,但这题提供了一个新的思路就是把各个维度拿出来单独考虑。
首先要注意到,这题其实可以看作两个子问题:
- 让自己活下来
- 让大佬自信值为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;
}

浙公网安备 33010602011771号