P3943 星空(状压DP+bfs+异或差分)

时间:2021-08-11
本文章向大家介绍P3943 星空(状压DP+bfs+异或差分),主要包括P3943 星空(状压DP+bfs+异或差分)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

命运偷走如果只留下结果, 时间偷走初衷只留下了苦衷。
你来过,然后你走后,只留下星空。

洛谷题目传送门

题目描述

逃不掉的那一天还是来了,小 F 看着夜空发呆。

天上空荡荡的,没有一颗星星——大概是因为天上吹不散的乌云吧。

心里吹不散的乌云,就让它在那里吧,反正也没有机会去改变什么了。

小 C 拿来了一长串星型小灯泡,假装是星星,递给小 F,想让小 F 开心一点。不过,有着强迫症的小 F 发现,这串一共 n 个灯泡的灯泡串上有 k 个灯泡没有被点亮。小 F 决定和小 C 一起把这个灯泡串全部点亮。

不过,也许是因为过于笨拙,小 F 只能将其中连续一段的灯泡状态给翻转——点亮暗灯泡,熄灭亮灯泡。经过摸索,小 F 发现他一共能够翻转 m 种长度的灯泡段中灯泡的状态。

小 C 和小 F 最终花了很长很长很长很长很长很长的时间把所有灯泡给全部点亮了。他们想知道他们是不是蠢了,因此他们找到了你,让你帮忙算算:在最优的情况下,至少需要几次操作才能把整个灯泡串给点亮?

解题思路

把未点亮的位置用1表示,点亮的位置用0表示

Step 1

首先有一些区间翻转操作,考虑怎么快速维护
有一种神奇的东东叫做异或差分,顾名思义就是每个位置和上个位置的异或值
例如原数列为
10010110
那么差分数列为
110111010
注意差分数列要到第n+1位
那么有了这个数列,在区间反转时只需要转换为对后面的影响
也就是在差分数组第l位异或一,在第r+1位异或一
最终把差分数组前缀异或出来就可以得到原数组
例如区间反转2~5,
则差分数组为100110010
得到的原数组为11101110
可以发现正好反转了
所以我们把题目给的原状态的异或差分数组求出,当差分数组全部为0时,可知此时原数组也全为0,也就是目标状态

Step 2

那么区间反转操作可以看成将序列中两个位置去翻,很容易得知如果这两个位置都是0,那么无意义
分两种讨论

1:一个为0,一个为1

那么取反之后还是一个为0,一个为1,但是交换了位置,也可以看成1移动了位置

2:都为1

直接消掉就可以了
于是我们可以预处理出消除某两个1的最少花费,也就是对于每种操作,在每个位置连一条边权为1的边,那么两点间的最短路就是最少花费,但是跑最短路有个log,较慢
我们知道边权为1的最短路和bfs是等价的,且bfs是没有log的,所以bfs就可以了

Step 3

回归正题,这个题是个状压DP
设F[S]表示要求点亮的位置的状态,由于是在异或差分数组中,所以要最多有$2\times k$个1
则状态转移就很简单了,就是选取两个没被点亮的把它点亮,取最小花费就可以啦

#include<bits/stdc++.h>
using namespace std;
const int N = 4e4+7;
int n,k,m;
int a[N],pos[700],opt[700],d[N];
int dis[N],len[200][200];
int f[1<<(18)],tot=0;
queue<int> q;
bool vis[N];
void bfs(int st)
{
	for(int i=1;i<=n+1;i++)
	{
		dis[i]=99999999;
		vis[i]=0;
	}
	dis[st]=0;
	vis[st]=1;
	q.push(st);
	while(!q.empty())
	{		
		int x=q.front();
		q.pop();
		for(int i=1;i<=m;i++)
		{
			int l=x-opt[i];
			int r=x+opt[i];
			if(l>=1&&!vis[l])
			{
				dis[l]=dis[x]+1;
				vis[l]=1;
				q.push(l);
			}
			if(r<=n+1&&!vis[r])
			{
				dis[r]=dis[x]+1;
				vis[r]=1;
				q.push(r);
			}
		}
	}
	
}
int main()
{
	scanf("%d%d%d",&n,&k,&m);
	for(int i=1;i<=k;i++)
	{
		int x;
		scanf("%d",&x);
		a[x]=1;
	}
	for(int i=1;i<=n+1;i++)
	{
		d[i]=a[i-1]^a[i];
		if(d[i]) pos[++tot]=i;
	}
	for(int i=1;i<=m;i++)
	scanf("%d",&opt[i]);
	for(int i=1;i<=tot;i++)
	{
		bfs(pos[i]);
		for(int j=1;j<=tot;j++)
		{
		  len[i][j]=dis[pos[j]];		
		}
	
	}
	memset(f,12,sizeof(f));	
	f[(1<<tot)-1]=0;
	for(int i=(1<<tot)-2;i>=0;i--)
	{
		for(int x=1;x<=tot;x++)
		{
			if(!((i>>(x-1))&1)) 
			{
				for(int y=x+1;y<=tot;y++)
				{
					if(!((i>>(y-1))&1))
					{
						f[i]=min(f[i],f[i+(1<<(x-1))+(1<<(y-1))]+len[x][y]);	
					
					}
								
				}

			}
		}
	}
	cout<<f[0];
	return 0;
 } 

原文地址:https://www.cnblogs.com/jesoyizexry/p/15130048.html