Codeforces 1741G 最短路上状压dp

题意:

n n n个地方,他们被 m m m条道路相连。有一天, t o t tot tot个人在 1 1 1处开派对,开完派对他们要回家,他们回家只会走最短路径,其中有 k ( k ≤ 6 ) k(k\leq6) k(k6)个人没车,而有车的人可以捎带这些没车的人,但前提是他们的家在开车的人的回家的路径上。问最少有多少个人只能走路回家?

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 i1个人的捎带集合 s s s是有可能的情况下

d p [ i ] [ s ∣ s i ] = t r u e dp[i][s|s_{i}]=true dp[i][ssi]=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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值