题意:
有nnn个地方,他们被mmm条道路相连。有一天,tottottot个人在111处开派对,开完派对他们要回家,他们回家只会走最短路径,其中有k(k≤6)k(k\leq6)k(k≤6)个人没车,而有车的人可以捎带这些没车的人,但前提是他们的家在开车的人的回家的路径上。问最少有多少个人只能走路回家?
Soluton:
显然我们只需要处理出每个开车的人可能捎带的组合,然后状压dpdpdp即可,设dp[i][s]dp[i][s]dp[i][s]为前iii个人,他们能捎带的总集合为sss是否有可能,我们处理完每个开车的人的捎带组合sis_{i}si,那么在前i−1i-1i−1个人的捎带集合sss是有可能的情况下
dp[i][s∣si]=true dp[i][s|s_{i}]=true dp[i][s∣si]=true
那么问题就在如何捎带出来,一种方法是先处理出最短路径长度,然后dfsdfsdfs找出每条路径可稍带的组合,然后dpdpdp,这样的时间复杂度太大,本来过得了,加了hackhackhack数据就过不了了,这是dfs版本代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=10005,inf=0x3fffffff;
const long long INF=0x3f3f3f3f3f3f,mod=998244353;
struct way
{
int to,next;
}edge[N<<1];
int cnt,head[N];
void add(int u,int v)
{
edge[++cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
int n,m,tot,k,walk[N],loc[N],dis[N],ccnt,disall;
bool car[N],dp[N][1<<6],vis[N];
vector<vector<int>>from(N);
void dijkstra()
{
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;
dis[1]=0;
q.push({dis[1],1});
while(!q.empty())
{
int u=q.top().second; q.pop();
if(vis[u]) continue;
vis[u]=true;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(dis[u]+1<dis[v])
{
from[v].clear();
from[v].push_back(u);
dis[v]=dis[u]+1;
q.push({dis[v],v});
}
else if(dis[u]+1==dis[v]) from[v].push_back(u);
}
}
}
int find(int u)
{
int ret=0;
for(int i=1;i<=k;i++)
if(loc[walk[i]]==u) ret|=1<<(i-1);
return ret;
}
int calc(int s)
{
int ret=0;
for(int i=0;i<k;i++) ret+=(s>>i)&1^1;
return ret;
}
void dfs(int u,int s,int ddis)
{
int ns=s|find(u);
if(u==1)
{
for(int i=0;i<(1<<k);i++)
if(dp[ccnt-1][i]) dp[ccnt][i|s]=true;
return;
}
if(ddis>=disall) return;
for(auto v:from[u]) dfs(v,ns,ddis+1);
}
void init()
{
cin>>n>>m; cnt=ccnt=0;
for(int i=1;i<=n;i++)
{
head[i]=0;
vis[i]=false;
dis[i]=inf;
from[i].clear();
}
for(int i=1;i<=m;i++)
{
int u,v; scanf("%d%d",&u,&v);
add(u,v); add(v,u);
}
scanf("%d",&tot);
for(int i=1;i<=tot;i++)
{
scanf("%d",&loc[i]);
car[i]=true;
}
scanf("%d",&k);
for(int i=1;i<=k;i++)
{
scanf("%d",&walk[i]);
car[walk[i]]=false;
}
for(int i=0;i<=tot;i++)
for(int j=0;j<(1<<k);j++) dp[i][j]=false;
dijkstra();
}
void work()
{
init();
for(int i=1;i<=tot;i++)
{
if(!car[i]) continue;
dp[ccnt++][0]=true;
disall=dis[loc[i]];
dfs(loc[i],find(loc[i]),0);
}
int ans=k;
for(int s=0;s<(1<<k);s++)
if(dp[ccnt][s]) ans=min(ans,calc(s));
printf("%d\n",ans);
}
int main()
{
int T; cin>>T;
while(T--) work();
return 0;
}
考虑优化他,可以在最短路的过程上再状压dpdpdp,设take[u][s]take[u][s]take[u][s]为从1到uuu的最短路上捎带集合为sss是否有可能,显然一开始take[u][0]=truetake[u][0]=truetake[u][0]=true,那么在dijkstra中,用uuu更新vvv的时候,就可以把uuu的take更新到vvv去,此时需要先情况take[v]take[v]take[v],或者在dis[u]+1==dis[v]dis[u]+1==dis[v]dis[u]+1==dis[v]的时候更新vvv,此时不用清空take[v]take[v]take[v]
需要注意的是,状压的是第几个没车的人的被捎带情况如何,而不是第几个人,这题的映射有点多
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=10005,inf=0x3fffffff;
const long long INF=0x3f3f3f3f3f3f,mod=998244353;
struct way
{
int to,next;
}edge[N<<1];
int cnt,head[N];
void add(int u,int v)
{
edge[++cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
int n,m,tot,k,loc[N],dis[N],live[N],ccnt;
bool take[N][1<<6],vis[N],car[N],dp[N][1<<6];
void clear(int u)
{
for(int i=1;i<(1<<k);i++) take[u][i]=false;
}
void upd(int u,int v)
{
for(int i=0;i<(1<<k);i++)
if(take[u][i]) take[v][i|live[v]]=true;
}
void dijkstra()
{
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;
q.push({dis[1]=0,1});
while(!q.empty())
{
int u=q.top().second; q.pop();
if(vis[u]) continue;
vis[u]=true;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(dis[u]+1<dis[v])
{
clear(v); upd(u,v);
q.push({dis[v]=dis[u]+1,v});
}
else if(dis[u]+1==dis[v]) upd(u,v);
}
}
}
int calc(int s)
{
int ret=0;
for(int i=0;i<k;i++) ret+=(s>>i)&1^1;
return ret;
}
void init()
{
scanf("%d%d",&n,&m);
cnt=ccnt=0;
for(int i=1;i<=n;i++) head[i]=live[i]=0;
while(m--)
{
int u,v; scanf("%d%d",&u,&v);
add(u,v); add(v,u);
}
scanf("%d",&tot);
for(int i=1;i<=tot;i++)
{
scanf("%d",&loc[i]);
car[i]=true;
}
scanf("%d",&k);
for(int i=1;i<=k;i++)
{
int x; scanf("%d",&x);
car[x]=false;
live[loc[x]]|=1<<(i-1);
}
for(int i=1;i<=tot;i++)
for(int j=0;j<(1<<k);j++) dp[i][j]=false;
for(int i=1;i<=n;i++)
{
for(int j=0;j<(1<<k);j++) take[i][j]=false;
take[i][0]=true; dis[i]=inf; vis[i]=false;
}
dijkstra();
}
void work()
{
init();
for(int i=1;i<=tot;i++)
{
if(!car[i]) continue;
dp[ccnt++][0]=true;
for(int j=0;j<(1<<::k);j++)
for(int k=0;k<(1<<::k);k++)
if(dp[ccnt-1][j]&&take[loc[i]][k]) dp[ccnt][j|k]=true;
}
int ans=k;
for(int i=0;i<(1<<k);i++)
if(dp[ccnt][i]) ans=min(ans,calc(i));
printf("%d\n",ans);
}
int main()
{
int T; cin>>T;
while(T--) work();
return 0;
}
最短路径DP
本文介绍了一个结合最短路径与状态压缩动态规划的问题解决方法。该问题涉及在一个有多个地点和道路连接的网络中,确定最少数量的人必须步行回家的情况。通过使用状态压缩技巧和最短路径算法,文章提供了一种高效求解方案。
236

被折叠的 条评论
为什么被折叠?



