Hetao P1178 冒险者 题解 [ 绿 ][ 最短路 ][ 线性 dp ]
题解
本蒟蒻采用的和大部分人解法不同,是根据当前标记值的总和跑最短路的一种解法。
思路 30min ,调代码 2h 的我太蒻了
首先观察题面可以发现本题求的是最少操作数,由于要求最小且有变化的过程,所以可以使用 dp 求解,也可以使用 最短路算法 求解,本篇先介绍最短路的算法。
其实作为图论来解还挺需要思维的。
最短路做法
建图
可以把本题抽象为以下的问题:
以编号为
实现
写题解的时候突然发现由于边权为 1 ,所以本题可以使用 bfs 用
但考试的时候没想那么多,就使用了 Dijkstra 算法,时间为
同时注意到每个数只能被减去一次,由于堆优化 dijkstra 的堆中每个情况是相对独立的,所以每个情况都要开额外的一个数组来存储每个数是否使用过,为了节省空间防止 MLE ,这里采用 bitset 来压缩空间。就是这里写错下标导致我调了 2h。
然后其他的就按照正常的最短路跑就行了。
代码
直接贺了赛时的 dijkstra 上去,什么时候有时间再来写 bfs 版和 dp 版的吧。
#include <bits/stdc++.h>
using namespace std;
int n,x;
int a[1005],start=0;
int dis[1001005];
bool vis[1001005];
struct node{
bitset<1005>bs;
int f,s;
}tmp,tmp2;
struct cmp{
bool operator()(node b,node c)
{
return c.f<b.f;
}
};
int dijkstra()
{
memset(dis,0x3f,sizeof(dis));
priority_queue<node,vector<node>,cmp> q;
tmp.f=0,tmp.s=start;
q.push(tmp);
dis[start]=0;
while(!q.empty())
{
tmp=q.top();
q.pop();
int oridis=tmp.f,u=tmp.s;
if(u%x==0)
{
return dis[u];
}
if(vis[u])continue;
vis[u]=1;
for(int i=1;i<=n;i++)
{
if(tmp.bs[i]==0)
{
int v=u-a[i];
if(dis[v]>oridis+1)
{
dis[v]=oridis+1;
tmp2.f=dis[v],tmp2.s=v;
tmp2.bs=tmp.bs;
tmp2.bs[i]=1;//就是这个下标坑我2h
q.push(tmp2);
}
}
}
int v=u+1;
if(v<=start+1000 && dis[v]>oridis+1)//最多操作次数为1000次
{
dis[v]=oridis+1;
tmp2.f=dis[v],tmp2.s=v;
tmp2.bs=tmp.bs;
q.push(tmp2);
}
}
return n;
}
int main()
{
freopen("player.in","r",stdin);
freopen("player.out","w",stdout);
cin>>n>>x;
for(int i=1;i<=n;i++)
{
cin>>a[i];
start+=a[i];
}
int res=dijkstra();
cout<<res;
return 0;
}
dp 做法
没有想到这个做法的原因主要还是最近 dp 有点生疏,一直往利用元素的条件来想,而忽视了靠元素的数值来解决问题。
这是典型的以数字设计 dp ,而不是用元素设计的一道题,从
同时,对于一个数删不删除,对应着 2 种状态,并且一旦知道删了哪些数,就可以推出这时要加多少个 1 。所以,就此设计转移方程式即可。
第一维是一个常见的 dp 设计思路:对于前
于是,定义
转移方程式为:
最后计算答案时,直接输出
然后还要注意一点:初始化
时间
未优化代码如下:
#include <bits/stdc++.h>
using namespace std;
int a[1005],n,f[1005][1005],x;
int main()
{
freopen("player.in","r",stdin);
freopen("player.out","w",stdout);
memset(f,0x3f,sizeof(f));
f[0][0]=0;
cin>>n>>x;
for(int i=1;i<=n;i++)
{
cin>>a[i];
a[i]%=x;
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<x;j++)
{
f[i][j]=min(f[i-1][j]+1,f[i-1][(j-a[i]+x)%x]);
}
}
int ans=0x3f3f3f3f;
for(int i=0;i<x;i++)
{
ans=min(ans,f[n][i]+(x-i)%x);
}
cout<<ans;
return 0;
}
优化
注意到动态转移方程式的第一维只会用到 虽然不这样做也可以过就是了。
滚动数组优化代码:
#include <bits/stdc++.h>
using namespace std;
int a[1005],n,f[2][1005],x;
int main()
{
freopen("player.in","r",stdin);
freopen("player.out","w",stdout);
memset(f,0x3f,sizeof(f));
f[0][0]=0;
cin>>n>>x;
for(int i=1;i<=n;i++)
{
cin>>a[i];
a[i]%=x;
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<x;j++)
{
f[i&1][j]=min(f[(i&1)^1][j]+1,f[(i&1)^1][(j-a[i]+x)%x]);//i&1=i%2,(i&1)^1=(i+1)%2
}
}
int ans=0x3f3f3f3f;
for(int i=0;i<x;i++)
{
ans=min(ans,f[n&1][i]+(x-i)%x);
}
cout<<ans;
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战