1 条题解

  • 0
    @ 2025-8-24 21:41:18

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar FCBM71
    AFO

    搬运于2025-08-24 21:41:18,当前版本为作者最后更新于2020-01-21 00:00:25,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    状压 + 最短路板子题(似乎不状压都可以过)。但感觉楼下的几位都讲的不是太详细,于是决定补发一波萌新友好型题解。适合还没有怎么接触过状态压缩和状态最短路的童鞋们。

    如果你还不熟悉位运算六大运算符(&,|,~,^,<<,>>),可以先自行百度。

    Step1. 怎么最短路?

    这道题可能和平常我们熟知的最短路不太一样。节点不是现成的节点,边不是现成的边。

    那么这道题中的节点是什么呢?是状态。状态就是指bug修复的状态。最开始,所有bug都还没有被修复,因此初始状态是 全部未修复 。通过运行某些补丁包,可以修复一些bug或者新增一些bug,那么此时的bug修复状态就是当前状态。我们就可以用最短路算法来维护从初始状态(全bug)到当前状态(修复了某些bug) 的最短路径。
    你可能还会问,我们怎么加边呢?这道题不需要加边,每次只需要枚举每一个补丁包即可(类似于完全图)
    那路径长度是什么呢?不就是运行补丁包的总时间吗。
    终点是什么呢?很显然,就是修复全部的Bug。

    Step2. 状态压缩

    由于一共有最多20个bug,如果每个bug都有已经修复和没有修复两种状态,那么每次状态转移需要传20个参(20元数组)。能不能把这一过程优化到1个参数呢?我们需要状态压缩。由于对于每个bug都只有修复没修复两种状态,我们不妨用0,1表示(1表示未修复,0已修复)。如果我们把这20个0,1串在一起就得到了一个二进制串,一个二进制串也就对应着一个整数。这样我们就把一个状态压缩成了一个整数(int)
    我们以样例为例,假设当前一种状态是(已修复bug12,未修复bug3),那么对应的01串就是110,转化成整数就是6。那么我们就可以知道:初始的bug全部未修复状态为111,对应7。结束的bug全部修复状态为000,对应0。将情况拓展到 nn 个bug,那么初始状态就是 2n12^n-1,即(1<<n)-1。结束状态是 00

    Step3. 状态转移

    说完了压缩再来说说转移。转移说白了就是根据当前的状态,计算出修复了bug之后的状态。

    判断能否使用一个补丁包

    翻译成状态语言就是,只有当前状态的某些位置上全部是1,某些位置上全部是0,才可以运行补丁包。如何进行这一判断呢? 我们可以把一个补丁包的使用先决条件也压缩成两条状态。样例中补丁包3的使用条件b1就是000=0,b2就是011=3.

    记当前状态为x
    1.判断是不是所有的b1位都是1:我们可以把b1与x按位与。容易知道,如果的得到的值就是b1本身,那么x所有b1位上都是1
    2.判断是不是所有的b2位都是0:我们可以把b1与x按位与。容易知道,如果的得到的值就是0,那么now所有b2位上都是0

    代码:(p是补丁包结构体)

     if((x&p[i].b1)==p[i].b1&&(x&p[i].b2)==0) 执行下一步
    

    使用了一个补丁之后的情况

    翻译成状态语言就是,使用之后将f1位置上变成0,f2位置上变成1。我们还是按照之前的思路,将f1,f2也计算成两个状态。如果需要将f2位置变成1,那么只需要用当前状态或上f2。如果需要将f1位置变成0,我们可以或上一个f1,使得当前状态的所有f1位置都变成1,再异或一个f1,这样就可以将f1所有位置变成0.

    代码:(y代表运行补丁包之后的状态)

    int y=((x|p[i].f1)|p[i].f2)^p[i].f1;
    

    Step 4. 贯穿

    主要思路讲完了,接下来就是完整的代码。

    #include<cstdio>
    #include<queue>
    #include<cstring>
    using namespace std;
    
    struct pack{int f1,f2,b1,b2,t;}p[505];
    int n,m,fir,minn[1<<22],tag;  //由于最多有2^20种状态,数组要开够
    queue<int>q;bool exi[1<<22];
    
    inline void read(int &x){
       char ch=getchar();
       while(ch<'0'||ch>'9')ch=getchar();
       x=ch^48;ch=getchar();
       while(ch>='0'&&ch<='9'){
       	x=(x<<3)+(x<<1)+(ch^48);
       	ch=getchar();
       }
    }
    
    inline int gtag(){  //手写读入可以防止出错
       char ch=getchar();
       while(ch!='+'&&ch!='-'&&ch!='0')ch=getchar();
       if(ch=='+')return 1;
       if(ch=='-')return 2;
       return 0;
    }
    
    void spfa(){  //关于spfa,它还没死
       memset(minn,0x7f,sizeof(minn));
       minn[fir]=0;
       q.push(fir);
       while(!q.empty()){
       	int x=q.front();
       	for(int i=1;i<=m;++i)  //枚举每一个包
       	 if((x&p[i].b1)==p[i].b1&&(x&p[i].b2)==0){  //判断先决
       	 	int y=((x|p[i].f1)|p[i].f2)^p[i].f1;//得到运行后状态
       	 	if(minn[x]+p[i].t<minn[y]){
       	 		minn[y]=minn[x]+p[i].t;
       	 		if(!exi[y]){
       	 			q.push(y);
       	 			exi[y]=true;
       	 		}
       	 	}
       	 }
       	exi[x]=false;
       	q.pop();
       }
    }
    
    int main(){
       read(n);read(m);
       for(int i=1;i<=m;++i){
       	read(p[i].t);
       	for(int j=1;j<=n;++j){
       		tag=gtag();
       		if(tag==1)p[i].b1|=(1<<j-1);  
       		if(tag==2)p[i].b2|=(1<<j-1);  //得到每个补丁包的先决条件串
       	}
       	for(int j=1;j<=n;++j){
       		tag=gtag();
       		if(tag==1)p[i].f2|=(1<<j-1);
       		if(tag==2)p[i].f1|=(1<<j-1);  //得到每个补丁包运行之后的状态串
       	}
       }
       fir=(1<<n)-1;   //得到初始状态
       spfa();
       if(minn[0]==minn[(1<<22)-1])printf("0");  //如果根本到达不了目标状态,就是无解
        else printf("%d",minn[0]); 
       return 0;
    }
    
    • 1

    信息

    ID
    1803
    时间
    1000ms
    内存
    512MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者