题意:
有 n n n个地方,他们被 m m m条道路相连。有一天, t o t tot tot个人在 1 1 1处开派对,开完派对他们要回家,他们回家只会走最短路径,其中有 k ( k ≤ 6 ) k(k\leq6) k(k≤6)个人没车,而有车的人可以捎带这些没车的人,但前提是他们的家在开车的人的回家的路径上。问最少有多少个人只能走路回家?
Soluton:
显然我们只需要处理出每个开车的人可能捎带的组合,然后状压 d p dp dp即可,设 d p [ i ] [ s ] dp[i][s] dp[i][s]为前 i i i个人,他们能捎带的总集合为 s s s是否有可能,我们处理完每个开车的人的捎带组合 s i s_{i} si,那么在前 i − 1 i-1 i−1个人的捎带集合 s s s是有可能的情况下
d p [ i ] [ s ∣ s i ] = t r u e dp[i][s|s_{i}]=true dp[i][s∣si]=true
那么问题就在如何捎带出来,一种方法是先处理出最短路径长度,然后 d f s dfs dfs找出每条路径可稍带的组合,然后 d p dp dp,这样的时间复杂度太大,本来过得了,加了 h a c k hack hack数据就过不了了,这是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;
}
考虑优化他,可以在最短路的过程上再状压 d p dp dp,设 t a k e [ u ] [ s ] take[u][s] take[u][s]为从1到 u u u的最短路上捎带集合为 s s s是否有可能,显然一开始 t a k e [ u ] [ 0 ] = t r u e take[u][0]=true take[u][0]=true,那么在dijkstra中,用 u u u更新 v v v的时候,就可以把 u u u的take更新到 v v v去,此时需要先情况 t a k e [ v ] take[v] take[v],或者在 d i s [ u ] + 1 = = d i s [ v ] dis[u]+1==dis[v] dis[u]+1==dis[v]的时候更新 v v v,此时不用清空 t a k e [ 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;
}