P9504题解
szh_AK_all · · 题解
传送门
思路分析
看到这个题第一眼时,我想到了暴力枚举(深搜)。
因为一共有
深搜的过程是,因为一共有
Code
#include<iostream>
#include<algorithm>
using namespace std;
struct tu
{
int a,b,c;
}s[40005];
int ans;
int vis[40005];
int k[40005];
int n,m,ss,t;
bool cmp(tu l,tu r)
{
if(l.a!=r.a)
return l.a<r.a;
return l.b<r.b;
}
int an=1000000000;
void dfs(int num,int sum,int ww)
{
if(ww<0)
return;
if(num==t)
{
if(sum<an&&ww==0)
an=sum;
return;
}
if(ww<=0)
return;
for(int i=k[num];i<=2*m;i++)
{
if(s[i].a!=num)
break;
if(vis[s[i].b]==0&&ww<=s[i].c)
{
vis[s[i].b]=1;
dfs(s[i].b,sum+(s[i].c/ww),ww-1);
vis[s[i].b]=0;
}
}
}
int main()
{
cin>>n>>m>>ss>>t;
for(int i=1;i<=m;i++)
{
int x,y,z;
cin>>x>>y>>z;
ans++;
s[ans].a=x;
s[ans].b=y;
s[ans].c=z;
ans++;
s[ans].a=y;
s[ans].b=x;
s[ans].c=z;
}
sort(s+1,s+1+ans,cmp);
for(int i=1;i<=ans;i++)
{
if(s[i].a!=s[i-1].a)
{
k[s[i].a]=i;
}
}
vis[ss]=1;
long long tmp=10000000000;
for(int i=1;i<=m;i++)
{
an=100000000;
dfs(ss,0,i);
if(an<tmp)
tmp=an;
}
cout<<tmp;
}
当然,在正常情况下,暴力枚举是会超时的,不出意外,这份代码只能通过第一个捆绑测试点。那有什么更好的方法呢?
本人发现,可以用深搜来做的题一般也可以用背包做。仔细思考一下,转移方程还是很好构造的。我们用一个二维背包来记录当每个点拥有每个魔法值时,需要多少生命到达终点。
不难发现,我们枚举每条边,边上的其中一个点要到达终点,可以经过边上的另一个点到达终点。那么我们得出了方程(用背包的第一位记录这是哪个节点,用第二维来记录魔法值):
数组
对于方程,我们可以这样理解:因为没从一个点到另一个点,生命值都会减去边权除以当前魔法值,所以,为了保证生命尽可能小而又不会小于
需要注意的是,我们尽可能将背包的初始值设大,但因为从终点到达终点的生命值只需要
因为每只魔兽的攻击力是不超过
AC 代码
#include<iostream>
#include<algorithm>
using namespace std;
int a[80005][105];
int u[80005],v[80005],w[80005];
int main()
{
int n,m,s,t;
cin>>n>>m>>s>>t;
for(int i=1;i<=m;i++)
{
cin>>u[i]>>v[i]>>w[i];
u[i+m]=v[i];
v[i+m]=u[i];
w[i+m]=w[i];
}
for(int i=1;i<=n;i++)
for(int j=0;j<=100;j++)
a[i][j]=100000000;
a[t][0]=0;
for(int i=1;i<=100;i++)
for(int j=1;j<=2*m;j++)
{
a[u[j]][i]=min(a[u[j]][i],a[v[j]][i-1]+(w[j]/i));
}
int ans=100000000;
for(int i=1;i<=100;i++)
ans=min(ans,a[s][i]);
for(int i=1;i<=n;i++)
ans=min(ans,a[i][100]);
cout<<ans;
}
注意,之所以在读入时增加了三行,是因为,我们读入的第一个点可以到达第二个点,而第二个点也可以到达第一个点。我们将每条边看为一条有向边,则一共有