『差分思想』BZOJ3003:LED

P r o b l e m \mathrm {Problem} Problem

LED屏是由一个庞大的点阵小灯泡组成的,一开始每个小灯泡都不发光。

每一行一共有N个小灯泡,依次标号为1~n。现在给定K个点,要求这K个点发光,其余点必须保持熄灭状态。

而这块LED屏的操作方式各种奇葩,一共有L种操作方法,第i种表示你能将任意长度恰为A_i的连续一段灯泡的状态取反(灭变亮,亮变灭)。

已知LED屏一共有m行,为了节省时间,请你算出每一行达到目标状态所需的最少操作次数

S o l u t i o n \mathrm{Solution} Solution

对于这道题,我们的主要思路就是讲区间取反转化为单点取反。

如果对于区间 [ l , r ] [l,r] [l,r]取反,我们可以维护差分数组 v v v,分别对 v l v_l vl v r + 1 v_{r+1} vr+1取反,再维护前缀和即可得到区间取反的操作,这样,我们就将区间操作转化为了单点操作。

那么显然对于初始序列的差分序列,你只需要读入 x x x以后将 x x x x + 1 x+1 x+1这两个位置分别做取反操作即可。

紧接着,我们要考虑如何将差分数组变成0.

我们发现如果我们要同时消掉两个数。例如,长度是 3 3 3 5 5 5,两个1是位置 1 1 1 9 9 9.

肯定是 1 1 1 4 4 4消一次, 1 1 1变成 0 0 0, 4 4 4变成 1 1 1,再 4 4 4 9 9 9消一次, 4 4 4变成 0 0 0, 9 9 9变成 0 0 0.因此两点间消掉的最少次数就是用给定的长度跳转的最短路径。

然后我们考虑状压, f [ s ] f[s] f[s]表示状态 S S S的灯已经熄灭的最少花费。我们可以任意找一盏开着的灯,在其它灯中找一个花费最少的决策来转移即可。

就像这样:
在这里插入图片描述

C o d e \mathrm{Code} Code

#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
const int K = 22;
const int N = 200000;

int n, k, L;
int m = 0;
int a[K], v[N], len[N], w[K], used[N];
int dis[N], vis[N], f[1<<K], d[K][K];

priority_queue< pair<int,int> > q;

int read(void)
{
	int s = 0, w = 0; char c = getchar();
	while (c < '0' or c > '9') w |= c == '-', c = getchar();
	while (c >= '0' and c <= '9') s = s*10+c-48, c = getchar();
	return w ? -s : s;
}

void work(void)
{
	n = read(), k = read(), L = read();
	m = 0;
	memset(d,30,sizeof d);
	memset(v,0,sizeof v);
	memset(used,0,sizeof used);
	for (int i=1;i<=k;++i)
	{
		a[i] = read();
		if (used[a[i]]) continue;
		used[a[i]] = 1;
		v[a[i]] ^= 1; v[a[i]+1] ^= 1;
	}
	for (int i=1;i<=L;++i) len[i] = read();
	for (int i=1;i<=n+1;++i)
	    if (v[i]) w[m++] = i;
	sort(w,w+m);
	for (int i=0;i<m;++i)
	{
		memset(vis,0,sizeof vis);
		memset(dis,30,sizeof dis);
		while (q.size()) q.pop();
		q.push(make_pair(0,w[i])), dis[w[i]] = 0;
		while (q.size())
		{
			int x = q.top().second; q.pop();
			if (vis[x]) continue; vis[x] = 1;
			for (int j=1;j<=L;++j)
			{
				if (x-len[j] >= 1 && dis[x]+1 < dis[x-len[j]]) {
					dis[x-len[j]] = dis[x] + 1;
					q.push(make_pair(-dis[x-len[j]],x-len[j]));
				}
				if (x+len[j] <= n+1 && dis[x]+1 < dis[x+len[j]]) {
					dis[x+len[j]] = dis[x] + 1;
					q.push(make_pair(-dis[x+len[j]],x+len[j]));
				}
			}
		}
		for (int j=0;j<m;++j) d[i][j] = dis[w[j]];
	}
	memset(f,30,sizeof f);
	f[0] = 0;
	for (int i=1;i<1<<m;++i)
	{
		int t = 0;
		while (((i >> t) & 1) == 0) t ++;
		for (int j=t+1;j<m;++j)
		    f[i] = min(f[i],f[i^(1<<t)^(1<<j)]+d[t][j]);
	}
	if (f[(1<<m)-1] > 1e8) puts("-1");
	else printf("%d\n", f[(1<<m)-1]);
}

int main(void)
{
	int T = read();
	while (T --) work();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值