Codeforces Round 63 (Rated for Div. 2) F. Delivery Oligopoly dp+图论状态转移

100 篇文章 0 订阅
62 篇文章 0 订阅

题目链接: https://codeforces.com/contest/1155/problem/F

题意:

现在给你一个 14 14 14 个点的无向边双联通图,现在要你删掉一些边,使得留下来的边最少并且仍旧是一个边双联通图。

一个边双联通分量(也就是题目要求的边双联通图)要求任意两个点之间有至少两条不相交的路径可以达到。

做法:

表示题目真难啊…不看真的做不太出来,只是能大约摸到点门路而已。

自己在画图的时候其实可以感觉到最后得到的那个最简图大概的形式,就是一个环带环(一个环上又带了一个环)的样子。

然后!大佬就想到了一条一条添加的 d p dp dp 转移(下面我的代码里是用 l l l 表示的) l [ i ] [ j ] [ s ] l[i][j][s] l[i][j][s] 代表,从点 i i i 到点 j j j 是否存在一条状态为 s s s 的链, s s s 是一个二进制的点表示, 1 1 1 代表在这条链上 0 0 0 代表不在。

然后我们在做的时候,要先枚举这个状态 s s s 下的起点 s t st st 和终点 e n en en 。然后去找不在状态 s s s 里面的点,如果枚举状态 10010 10010 10010 下的起点 2 2 2 和终点 5 5 5 ,那么我们就要去找状态 01101 01101 01101 里面所有的状态 t m p tmp tmp 能否由此起点和终点连上,即 l [ 2 ] [ 5 ] [ t m p ] l[2][5][tmp] l[2][5][tmp] 是否存在。这里有一个快速枚举 t m p tmp tmp 的方法(反正我以前是没碰到过),代码里会写的应该有基础的都能看得懂。

在更新的时候,我们还需要保存下来当前的这个起点和终点,以及从哪个状态转移过来,这可以让我们在最后更新的时候快速找到答案。

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
#define rep_e(i,u) for(int i=head[u];~i;i=nex[i])
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int maxn=15;
const int maxm=maxn*maxn;
const int up=(1<<15)+10;
const int inf=1e9;
int N[up],dp[up],n,m,cnt,Fr[up],To[up],s[up];
int head[maxn],to[maxm],nex[maxm];
bool l[maxn][maxn][up];
vector<pii> road;
void add(int u,int v){
    to[cnt]=v;nex[cnt]=head[u];
    head[u]=cnt++;
}
void dfs(int u,int S,int st){
    if(S!=0&&u==st) return ;  //保证一每次搜索只有一个环
    rep_e(i,u){
        int v=to[i];
        if(S&(1<<(v-1))) continue;
        if(l[st][v][S]) continue;
        if(N[S]==1&&v==st) continue; //因为保证没有重边 所以1->2 2->1这样的情况要排除
        l[st][v][S]=1;
        dfs(v,S^(1<<(v-1)),st);
    }
}
void deal(int st,int en,int S){
    if(S==0){   //如果不经过其他任意的链 则直接添加
        road.push_back({st,en});
        return ;
    }
    rep_e(i,st){
        int v=to[i];
        if(S&(1<<(v-1))){
            int nexS=S^(1<<(v-1));
            if(l[v][en][nexS]){
                //如果从v->en也可以走到 那么直接转移
                road.push_back({st,v});
                deal(v,en,nexS);
                return ;
            }
        }
    }

}
int main() {
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    rep(i,1,m){
        int x,y; scanf("%d%d",&x,&y);
        add(x,y); add(y,x);
    }
    //状态i有多少个点 像__builtin_pop(i)一样但更快速
    rep(i,1,up-1) N[i]=N[i^(i&(-i))]+1;

    for(int i=1;i<=n;i++) dfs(i,0,i);

    int M=(1<<n);
    //单个点的情况是待延伸的情况
    //可能从任意一个单个点开始 故要初始化为0
    rep(i,1,M-1){
        dp[i]=(N[i]==1?0:inf);
    }
    for(int i=1;i<M;i++){
        for(int u=1;u<=n;u++){
            if((i&(1<<(u-1)))==0) continue;
            for(int v=u;v<=n;v++){
                if((i&(1<<(v-1)))==0) continue;
                int S=(M-1)^i;
                //!!快速遍历包括如状态1101内的所有状态
                for(int j=S;j;j=(j-1)&S){
                    if(l[u][v][j]){
                        int nex=i|j;
                        if(dp[nex]>dp[i]+N[j]+1){
                            dp[nex]= dp[i]+N[j]+1;
                            Fr[nex]=u,To[nex]=v,s[nex]=j;
                        }
                    }
                }
            }
        }
    }
    printf("%d\n",dp[M-1]);
    for(int i=M-1;N[i]>1;i=i^s[i]){
        deal(Fr[i],To[i],s[i]);
    }

    for(int i=0;i<road.size();i++){
        printf("%d %d\n",road[i].first,road[i].second);
    }
 	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值