P3943 星空

哇真的是,这道题目太妙了。想到了要用状压,想到了 bfs 的前置,没有用差分连起来,差分太妙了。

P3943 星空

题目大意:

给你一个 \(01\) 串,有 \(m\) 种长度可以反转,问最少几次可以将序列变为全一。

思路:

  1. 区间修改 \(\xRightarrow{差分}\) 单点修改。

  2. bfs求两点间最短路+状压存储状态。

  3. 利用状压求最终答案。

1. 区间修改 \(\xRightarrow{差分}\) 单点修改

首先,上来我们会发现,题目是要让我们进行区间操作,但是区间操作十分的麻烦,我们就可以想到差分。

但是普通的差分还是不好处理,因为一般的差分思路就是我们记原数组为 \(a\),差分数组为 \(b\),则对于某一次区间取反操作 \([l,r]\),我们就转化成 \(b_l\) ^ \(1\)\(b_{r+1}\) ^ \(1\)。之后 \(b\) 数组前缀异或处理后,再和 \(a\) 按位异或就好了。但是我们不确定要处理的区间,所以难以求解。

既然如此,那我们可以换个思路,就是所谓的“正难则反”。

我们可以考虑,我们想要的 \(b\) 数组的最终状态是什么,答案显然:若第 \(i\) 个位置未点亮,则 \(b_i\)\(b_{i+1}\) 需要 \(=1\)

聪明的人可能会想到两个未亮的灯相邻的特殊情况,对于这种情况:

未点亮:2,3
对应 a 数组:0 1 1 0 0 ……

那么对应的 \(b\) 应该为:

b:0 1 1   0 ……
       1 1 

=> 0 1 0 1 0 ……

你可以在存需要为 \(1\) 的位置的时候特判掉,也可以不用,下面会说(在 2. 的最后)。

并且,由于区间取反 \([l,r]\)\(b\) 数组上体现为 \(b_l\) ^ \(1\)\(b_{r+1}\) ^ \(1\),所以有以下三种转化:

  1. \(b_l\)\(b_{r+1}\) 同为 \(1\):相消。
  2. \(b_l\)\(b_{r+1}\) 其中一个为 \(1\):将某一端 \(1\) 移动到另一端。
  3. \(b_l\)\(b_{r+1}\) 同为 \(0\):无需操作(无意义)。

所以综上,题目就被转化为了:

\(2\times n\) 个位置为 \(1\),有 \(m\) 种将 \(1\) 移动的长度,\(1\)\(1\) 相遇则相消,问最少几步可以全消除。

注意!!这里有个坑,就是由于转化为差分数组,会出现 \(i+1\),所以总长度 \(n\)\(+1\)

之后就很套路了。

2. bfs 求两点间最短路+状压存储状态。

可以注意到 \(n\) 只有 \(8\),在转化为差分数组之后也只有 \(2\times n = 16\),显然是再提醒用状压来解决,那怎么用?

如果想用状压,就意味着只能存储是否消除,无法存储位置信息,那我们可以通过某种与处理的方式来实现初始化吗?

答案是可行的,因为如果最终某两个 \(1\) 相消了,我们先将这两个彻底消除之后再考虑剩下的一定没有影响。

这样,我们就可以预处理出任意两个 \(1\) 相互消除的代价。

并且,设 \(a\)\(b\) 相消的话,只需要让某一个动就好了,因为 \(a\) 向右,\(b\) 向左,在中间相遇,和 \(a\) 一路向右没有区别。

所以就可以以每一个点作为起点,做一遍 bfs (由于每一步的代价都是 \(1\),所以直接 bfs 就行),求出两两之间的最短路,并通过状压记录。

这里就可以发现为什么说不用在存需要为 \(1\) 的位置的时候将重合的位置特判掉,答案就是重合的位置最短路等于 \(0\) 不影响最终答案。

inline void bfs(int st)
{
	memset(dis,0x3f,sizeof(dis));
	memset(ck,0,sizeof(ck));
	dis[st]=0;	ck[st]=1;	q.push(st);
	while(!q.empty())
	{
		now=q.front();	q.pop();
		for(int i=1;i<=m;i++)
		{
			cl(now,now-len[i]);
			cl(now,now+len[i]);
		}
	}
}

……
………………
…………

int main()
{
	…………
	……………………
	……
	memset(f,0x3f,sizeof(f));
	for(int i=1,ls;i<=num;i++)
	{
		bfs(w[i]);
		for(int j=1;j<=num;j++)
		{
			if(i==j)	continue;
			ls=(1<<(i-1))|(1<<j-1);
			f[ls]=min(f[ls],dis[w[j]]);
		}
	}
	…………
	……
}

3. 利用状压求最终答案。

最简单,最套路的一步,枚举状态,枚举上一次相互消掉的是哪两个位置,转移。

同时,由于一定是两两相消,所以任何有意义的状态一定有偶数个 \(1\),所以可以剔除掉不合法的状态,算是个小优化,不去除也不影响答案和复杂度的正确性。

inline bool asknum(int A){bool rt=0;while(A)rt^=1,A-=lowbit(A);return rt;}
//求当前这种状态有几个1。

……………

up=(1<<num)-1;	f[0]=0;
	for(int i=3,ls1,ls2;i<=up;i++)
	{
		if(asknum(i))	continue;	//小优化
		for(int j=1;j<num;j++)
		{
			ls1=1<<(j-1);
			if((i|ls1)!=i)	continue;
			for(int k=j+1;k<=num;k++)
			{
				ls2=1<<(k-1);
				if((i|ls2)!=i)	continue;
				f[i]=min(f[i],f[(i^ls1)^ls2]+f[ls1|ls2]);
			}
		}
	}

Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(x) (x&(-x))
inline int read(){
	int rt=0;	char g=getchar();
	while(g<'0'||g>'9')	g=getchar();
	while(g>='0'&&g<='9')	rt=(rt<<3)+(rt<<1)+g-'0',g=getchar();
	return rt;
}
int n,num,m,up;
int w[17],f[(1<<16)];
inline bool asknum(int A){bool rt=0;while(A)rt^=1,A-=lowbit(A);return rt;}

int len[70];
int dis[40004],now;
bool ck[40004];
queue<int>q;
inline void cl(int now,int to)
{
	if(1<=to&&to<=n&&!ck[to])
		dis[to]=dis[now]+1,ck[to]=1,q.push(to);
}
inline void bfs(int st)
{
	memset(dis,0x3f,sizeof(dis));
	memset(ck,0,sizeof(ck));
	dis[st]=0;	ck[st]=1;	q.push(st);
	while(!q.empty())
	{
		now=q.front();	q.pop();
		for(int i=1;i<=m;i++)
		{
			cl(now,now-len[i]);
			cl(now,now+len[i]);
		}
	}
}

int main()
{
	n=read()+1;	num=read()<<1;	m=read();
	for(int i=1;i<=num;i+=2)	w[i]=read(),w[i+1]=w[i]+1;
	for(int i=1;i<=m;i++)	len[i]=read();
	
	memset(f,0x3f,sizeof(f));
	for(int i=1,ls;i<=num;i++)
	{
		bfs(w[i]);
		for(int j=1;j<=num;j++)
		{
			if(i==j)	continue;
			ls=(1<<(i-1))|(1<<j-1);
			f[ls]=min(f[ls],dis[w[j]]);
		}
	}
	up=(1<<num)-1;	f[0]=0;
	for(int i=3,ls1,ls2;i<=up;i++)
	{
		if(asknum(i))	continue;
		for(int j=1;j<num;j++)
		{
			ls1=1<<(j-1);
			if((i|ls1)!=i)	continue;
			for(int k=j+1;k<=num;k++)
			{
				ls2=1<<(k-1);
				if((i|ls2)!=i)	continue;
				f[i]=min(f[i],f[(i^ls1)^ls2]+f[ls1|ls2]);
			}
		}
	}
	printf("%d",f[up]);
	return 0;
}
posted @ 2024-07-11 15:04  YT0104  阅读(4)  评论(0编辑  收藏  举报