Loading

状压dp学习笔记(紫例题集)

\(Noip\)前回来看自己写的博客了,发现阅读性很差,决定重新梳理一下

旅游景点 Tourist Attractions

这个代码其实不算是正规题解的(因为我蒟蒻)

是在自己\(oj\)上内存限制\(324MIB\)情况下过掉的,

而且经过研究感觉不太能用滚动数组,

所以那这个题学习一下状压\(dp\)思想还是勉强可以的

废话:

想了半天还是写一篇题解吧,尽管有点麻烦

但这题的确做了不下十几节课

不写一篇对不起这几天牺牲的公自了(惨)

\(UPD:\)之后的内容可能和之前不一样,做了一些删改,加入了现在的理解

这道题的主要思想是把图分成三个部分处理

先预处理出必须要经过的点和其他点之间的最短路,设为\(dis[i][j]\)

然后先对能够从起点\(1\)到达的点进行预处理,然后考虑对\([2,k+1]\)的点做状压\(dp\)

\(dp[i][j]\)表示所经过\([2,k+1]\)的点的集合为\(i\),最后一个到的点(也就是当前的点)是\(j\)时走过的最短路

在转移的时候枚举点集的状态,并枚举最后一个点和新加入集合的点\(j,u\),然后对新加入的点做一个类似背包的合并问题就可以了

注意的是转移时候要满足题目给的限制,那么预处理出的\(a\)数组就表示在一个点\(i\)之前必须要经过的点的集合

若当前枚举的点集\(i\)包含转移的点的\(a[u]\)集合就可以转移

最后处理出经过所有点的最短路后再对某一个点到终点的距离进行合并就做完了

代码里面的注释都是一些细节,原来写的一些我都删掉了,看着没大必要

code
#include<bits/stdc++.h>
using namespace std;
const int NN=200005,MM=20005;
int n,m,q,k;
struct SNOW{
	int to,next,value;
}e[NN<<1];
int r[NN<<1],tot=0;
int dis[25][MM],a[25];
bool v[MM];
int dp[1<<20][25];
struct snow{
	int q,data;
	friend bool operator<(snow a,snow b){return a.data>b.data;}
};
snow cc; priority_queue<snow> Q;
inline void add(int x,int y,int z){
	e[++tot]=(SNOW){y,r[x],z};
	r[x]=tot;
}
inline void WSN(int st){
	int x,y;
	cc.q=st ;cc.data=0;
	Q.push(cc);
	memset(v,false,sizeof(v));//一定记得bool数组清零
	dis[st][st]=0;
	while(!Q.empty()){
		x=Q.top().q;y=Q.top().data;
		Q.pop();
		if(!v[x]){
			v[x]=true;
			for(int i=r[x];i;i=e[i].next){
				cc.q=e[i].to;
				if(dis[st][cc.q]>y+e[i].value){
					dis[st][cc.q]=y+e[i].value;
					cc.data=dis[st][cc.q];
					Q.push(cc);
				}
			}
		}
	}
}
inline void init(){
	dp[0][1]=0;
	for(int i=2;i<=k+1;i++)
		if(!a[i]) dp[1<<(i-2)][i]=dis[1][i];
}
inline int read(){
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
	while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar();}
	return x*f;
}
void write(int x){
	if(x<0){ putchar('-'); x=-x;}
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
signed main(){
	memset(dis,50,sizeof(dis));//最大值都要赋小一点,否则会爆炸
	n=read(),m=read(),k=read();
	int mzs=1<<k;
	for(int i=1;i<=m;i++){
		int x=read(),y=read(),z=read();
		add(x,y,z); add(y,x,z);
	}
	if(k==0){WSN(1);write(dis[1][n]);putchar('\n');return 0;}
	for(int i=1;i<=k+1;i++) WSN(i);
	q=read();
	for(int i=1;i<=q;i++){
		int f=read(),l=read();
		if(f>(k+1)||l>(k+1)) continue;
		a[l]|=(1<<(f-2));
	}
	memset(dp,50,sizeof(dp));
	init();
	for(int i=0;i<mzs;i++)
		for(int j=0;j<k;j++)
			for(int u=0;u<k;u++){
				if((i&(1<<j))==0) continue;
				if((i&(1<<u))==0 && (i|a[u+2])==i)
					dp[i|(1<<u)][u+2]=min(dp[i|(1<<u)][u+2],dp[i][j+2]+dis[j+2][u+2]);
			}
	int wsn=0x3fffffff;
	for(int i=2;i<=k+1;i++)
		wsn=min(wsn,dp[(1<<k)-1][i]+dis[i][n]);
	write(wsn); putchar('\n');
	return 0;
}

动物园

人生中第二篇题解,因为这题的确很喵~

怎么说呢,感觉不用赋值最小值(用的用的)。。。

\(UPD:\)我去当时自己真\(sb\)

只记得当时是自己想出的如何解决用二进制位表示三种不同的情感,然后\(zxs\)想出了如何处理在环上的\(dp\)问题

然后没有颓题解把这道题给干掉了,高兴了好一阵子。。。。

发现这个状压还是非常隐含的,这题重点在信息的压缩,而不像是在练状压\(dp\)

题目中唯一可以状压的信息就是小朋友可以看到的围栏

那么考虑记录下每个小朋友喜欢的动物的集合不喜欢的集合

那么枚举所有可能的集合\(i\)表示对于每种动物选或者不选,那么处理出的两个变量和这个集合判断一下包含关系集合预处理出一个数组\(wsn\)

\(wsn[E][i]\)表示对于一段从\(E\)开始的可见的区间,在选择留下动物集合为\(i\)时候可以让小朋友高兴的个数

然后就可以考虑进行\(dp\),设\(dp[i][j]\)表示从\(i\)开始的一段可见区间的剩下动物集合为\(j\)时候最多可以让几个小朋友高兴

难点在如何枚举循环可以解决环上的问题

我们先枚举状态\(l\)表示要找的状态,然后每次预设开始状态转移,

这样保证了不会因为枚举环导致最后处理到\([n-5,n]\)的一段时和前面的\(dp\)状态重复计算

那么转移方程是比较好写的

\(dp[i][j]=max(dp[i-1][(j\&15)<<1],dp[i-1][(j\& 15)<<1|1])+wsn[i][j]\)

表示你的状态从上一种向前推进了一个围栏,可以理解为滑动窗口,然后对于新的动物选或者不选的状态下选出最大值加上贡献

code
#include<bits/stdc++.h>
#define int long long
using namespace std ;
const int MM=10005;
int N,C,snow,mzs=(1<<5);
int dp[MM][1<<5],wsn[MM][1<<5];
inline int read (){
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar();}
    return x*f;
}
void write(int x){
    if(x<0){ putchar('-'); x=-x;}
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
signed main()
{
    N=read(),C=read();
    for(int i=1;i<=C;i++)
    {
        int E=read(),F=read(),L=read(),hate=0,like=0;
        for(int j=1;j<=F;j++)hate|=1<<((read()-E+N)%N);
        for(int j=1;j<=L;j++)like|=1<<((read()-E+N)%N);
        for(int j=0;j<mzs;j++) if( (like&j) || (hate&~j) ) wsn[E][j]++;
    }
	//大的循环枚举不同的开始状态,因为是环,要用每个为开始状态进行找最值,因为最后会转回来(即有重复)
    for(int l=0;l<mzs;l++){
        for(int r=0;r<=32;r++) dp[0][r]=-999999999;
        dp[0][l]=0;
        for(int i=1;i<=N;i++)
            for(int j=0;j<mzs;j++)
                dp[i][j]=max(dp[i-1][(j&15)<<1],dp[i-1][(j&15)<<1|1])+wsn[i][j];
        snow=max(snow,dp[N][l]);//在情况内寻找最大值
    }
    write(snow); putchar('\n');
    return 0;
}

\(UPD\)重新对状压审视一下,发现这一类提的思路一般都是对于现有元素的集合然后再枚举新的元素进行合并

并对压缩的信息有了更好的认识,可能遇到状压可以考虑的更快了

posted @ 2021-06-16 18:46  雪域亡魂  阅读(94)  评论(0)    收藏  举报