【题解】星空【状压DP】

在这里插入图片描述
这道题思维难度非常高,有很多处理的小技巧,并且代码也有很多细节 ;

第一步

这道题是一种序列的区间操作,我们都知道,区间操作比较麻烦,所以我们要想办法将区间操作转换成单点修改;
这时,我们想到了差分,假如我们对一个序列进行操作,这时一个序列里的相对状态不会变只有两端改变,换句话说就是这个序列的差分并不会发生改变,只有两端的差分数组会发生变化。但这里的差分并不是差
而是异或;对一个区间[l,r]进行修改操作就是将差分数组c[l],c[r+1]取反,最后我们的目标就是要将差分数组全部变成0;
但这里就有一个细节:差分数组全部变成0的意思就是所有的状态都是一样的,但有可能全部亮或者全不亮;这里我们要假设第0个灯泡是亮的;然后差分出来才是合法的全亮状态;
还有一个细节:差分数组要开到n+1来保证至少有偶数个数的1,这样才能消去 ;
所以我们就这样完成了第一步,将区间修改转化成单点修改;

第二步

处理出来差分数组之后,我们就要将这个序列全部变为0 ;
有两种情况;一个0一个1,这样操作之后1的个数没有变 ;
两个都是1,这种情况就是减少两个1

但大多数情况并不能一次操作就完成,因为只能操作m种长度 ;
所以实际情况是1先和一些0交换,然后两个1一起消去 ;
所以对所有1跑一边BFS处理出到与其他1消去所需要的步数;
bfsO(n)就可以完成;

第三步

处理完每个1与其他1消去之后,就要考虑如何选择使其代价最少 ;
我们可以注意到其实原来的1很少不超过16个,于是我们可以开始考虑状压DP ;
我们反着DP;
dp[i]表示状态为i到状态为0所需要的最小步数 ;
初始化dp[0]=0 ;
然后倒着枚举点亮哪两个灯,(因为倒着来是点亮,正着来就是消去两个灯)
然后点亮完了的状态就是i|(1<<x1)|(1<<x2)
然后就可以得到DP转移方程:
int now=(i|(1<<fir))|(1<<j) ;
dp[now]=min(dp[now],dp[i]+getdis(fir+1,j+1)) ;

最后再输出dp[最初的状态] ;

到此,这道题就顺利的解决了;
具体细节请看代码:

#include<bits/stdc++.h>

using namespace std;

const int maxn = 40101 ;
int n,k,m ;
int a[maxn],c[maxn],dis[20][maxn] ;
int len[maxn],sta[maxn],top=0 ;
int dp[1<<17] ;
bool vis[maxn] ;

inline void read(int &x)
{
	int f=1 ;x=0 ;char s=getchar() ;
	while(s<'0'||s>'9'){if(s=='-')f=-1 ;s=getchar() ;	}
	while(s>='0'&&s<='9'){x=x*10+s-'0' ;s=getchar() ;	}
	x*=f ;
}


void bfs(int s,int *dis)
{
	memset(vis,0,sizeof(vis)) ;
	queue<int>q ;
	dis[s]=0 ;vis[s]=1 ;q.push(s) ;
	while(q.size())
	{
		int x=q.front() ;q.pop() ;
		for(int i=1;i<=m;++i)
		{
			int y=x-len[i] ;
			if(y>0&&!vis[y])
			{
				vis[y]=1;
				dis[y]=dis[x]+1 ;
				q.push(y) ;
			}
			y=x+len[i] ;
			if(y<=n+1&&!vis[y])
			{
				vis[y]=1 ;
				dis[y]=dis[x]+1 ;
				q.push(y) ;
			}
		}
	}
}

int getdis(int x,int y)
{
	return dis[x][sta[y]] ;
}
int main()
{
	
	//freopen("starlit.in","r",stdin) ;
	//freopen("starlit.out","w",stdout) ;
	read(n) ;read(k) ;read(m) ;
	memset(a,1,sizeof(a)) ;
	for(int i=1;i<=k;++i)
	{
		int x ;read(x) ;
		a[x]=0 ;
	}
	for(int i=1;i<=n+1;++i) c[i]=a[i]^a[i-1] ;
	for(int i=1;i<=m;++i) read(len[i]) ;
	memset(dis,0x3f,sizeof(dis)) ;
	memset(dp,0x3f,sizeof(dp)) ;
	
	for(int i=1;i<=n+1;++i)
		if(c[i]) sta[++top]=i,bfs(i,dis[top]) ;
	dp[0]=0 ;
	for(int i=0,t=(1<<top)-1;i<t;++i)
	{
		int fir=-1 ;
		for(int j=0;j<top;++j) if(!((i>>j)&1)) {fir=j ;break ;		}
		for(int j=fir+1;j<top;++j)
		{
			if(!((i>>j)&1))
			{
				int now=(i|(1<<fir))|(1<<j) ;
				dp[now]=min(dp[now],dp[i]+getdis(fir+1,j+1)) ;
			}
		}
	}
	printf("%d",dp[(1<<top)-1]) ;
	return 0 ;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值