【题解】LuoGu3943:星空

原题传送门
因为这个 k < = 8 k<=8 k<=8所以想到状压

主要思想是差分
令灯暗为1,灯亮为0
比如一行灯的情况是
a : 01001100 a:01001100 a:01001100
那么对应差分数组就是
b : 011010100 b:011010100 b:011010100
可以发现 a i = b 1 x o r b 2 x o r . . . x o r b i a_i=b_1xorb_2xor...xorb_i ai=b1xorb2xor...xorbi
然后我对于区间 [ l , r ] [l,r] [l,r]进行反转操作,在差分数组里的表现是
b l = b l x o r 1 , b r + 1 = b r + 1 x o r 1 b_l=b_lxor1,b_{r+1}=b_{r+1}xor1 bl=blxor1,br+1=br+1xor1

问题可以转化为把 b b b数组变成全部是0
发现 b b b数组1的个数一定是偶数个,且最多 2 k 2k 2k
可以考虑每次消除两个1,而消除这两个1的代价通过预处理求出,接下来就是状压

如果要把 10000001 10000001 10000001变成全0,但是我只能翻转长度为3、4
可以先 00010001 00010001 00010001表示对 [ 1 , 3 ] [1,3] [1,3]翻转
00000000 00000000 00000000表示对 [ 4 , 7 ] [4,7] [4,7]翻转
说明对于两个相差为 x x x 1 1 1,要把他俩变成0,选择 x = b 1 + b 2 + . . . + b y x=b_1+b_2+...+b_y x=b1+b2+...+by
多次翻转组合是可以的

或者要把 00110000 00110000 00110000变成全0,但是我只能翻转长度为3、4
可以先 00010010 00010010 00010010表示对 [ 3 , 6 ] [3,6] [3,6]翻转
00000000 00000000 00000000表示对 [ 4 , 6 ] [4,6] [4,6]翻转
说明翻转区间长度之间相减也是可以的

所以对于某一个长度,通过 b b b数组加加减减可以得到自己的长度,针对合理的情况中求出最少翻转次数,通过完全背包求得

Code:

#include <bits/stdc++.h>
#define maxn 40010
#define maxm 100010
using namespace std;
int a[maxn], dp[maxm], cost[maxn], b[maxn], pos[maxn], power[25], tot, n, m, k;

inline int read(){
	int s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

int main(){
	n = read(), k = read(), m = read();
	for (int i = 1; i <= k; ++i){
		int x = read();
		a[x] ^= 1, a[x + 1] ^= 1;
	}
	for (int i = 1; i <= n; ++i) cost[i] = 1e9;
	for (int i = 1; i <= m; ++i) b[i] = read();
	for (int i = 1; i <= m; ++i)
		for (int j = b[i]; j <= n; ++j) cost[j] = min(cost[j], cost[j - b[i]] + 1);
	for (int i = 1; i <= m; ++i)
		for (int j = n - b[i]; j >= 0; --j) cost[j] = min(cost[j], cost[j + b[i]] + 1);
	for (int i = 1; i <= n + 1; ++i)
		if (a[i]) pos[++tot] = i;
	power[0] = 1;
	for (int i = 1; i <= 20; ++i) power[i] = power[i - 1] << 1;
	for (int i = 1; i < power[tot]; ++i) dp[i] = 1e9;
	for (int i = 0; i < power[tot]; ++i)
		for (int j = 1; j <= tot; ++j)
			if (!(i & power[j - 1]))
				for (int k = j + 1; k <= tot; ++k)
					if (!(i & power[k - 1]))
						dp[i | power[j - 1] | power[k - 1]] = min(dp[i | power[j - 1] | power[k - 1]], dp[i] + cost[pos[k] - pos[j]]);
	printf("%d\n", dp[power[tot] - 1]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值