[模拟考试题][神题]Star Sky[状压DP][BFS][差分]

题意:

给你n个排成一排的灯,0代表开着,1代表关着,有k盏开着的灯。现在你有m种不同长度的电线,可以使得leni那么长的区间反转(0变1,1变0)。现在问你使得整个区间的灯全部开着的最少的操作数。

N <= 40000 K <= 8 M <= 64

真心神题。首先我们观察到序列上的操作都是对于一整段区间而言的,自然想到差分(自然个鬼啊!!),我们将序列用异或差分,然后对一个区间的取反操作变成了对端点的取反。满足条件的序列是全为0的。k很小,就想到在k上做文章。

分析差分序列上的操作:

1.前面有一个1后面有一个0:分别取反变成1 0,相当于1移到了0的位置上。

2.前1后1:将他们同时取反就可以接近目标序列

取反操作的前提是两个bit位相差的距离存在于m个len中,但是同样的,多次移动操作可以构造出合法距离来消掉1

所以想到以每一个差分序列上的1为起点,跑BFS处理它到达其他的1的最小操作数(到达了便消除了)

O(nm)建边

然后用状压DP找到消除1的最优操作顺序

真心神题。模型转化太强了。

首先你得把区间操作转化为差分,并且要想到最终状态给我们的启示——消掉1,然后还要想到用BFS处理距离,然后还要想到用状压处理操作顺序。接近省选题了吧。真心好题。%%%%%出题人

总复杂度O(nm + nmk + k * 2 ^ 2k) (状压没有处理好我T了4个点至今未解决)

#include <bits/stdc++.h>
using namespace std;

const int N = 40005;

int n, k, m, val[1 << 17], a[N], b[N], len[N], head[N], top, dis[20][N], pos[20], f[1 << 17];

struct Node
{
	int y, nxt;
	Node() {	}
	Node( int y, int nxt ) : y(y), nxt(nxt) {	}
} e[N * 129];

void Adde( int x, int y )
{
	e[++top] = Node(y, head[x]), head[x] = top;
	e[++top] = Node(x, head[y]), head[y] = top;
}

int h, t, que[N];

void Bfs( int st, int num )
{
	h = t = 0;
	memset(dis[num], 0x3f, sizeof(dis[num]));
	dis[num][st] = 0;
	que[++t] = st;
	while(h < t)
	{
		int u = que[++h];
		for(int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].y;
			if(dis[num][v] != 0x3f3f3f3f) continue;
			dis[num][v] = dis[num][u] + 1;
			que[++t] = v;
		}
	}
}

int Dfs( int sta )
{
	if(sta == 0) return 0;
	if(f[sta] != -1) return f[sta];
	f[sta] = 0x3f3f3f3f;
	int tmp = sta - ((sta) & (-sta)), cur = tmp, num1 = val[sta & (-sta)];
	while(cur) 
	{
		if(dis[num1][pos[val[cur & (-cur)]]] != 0x3f3f3f3f)
			f[sta] = min(f[sta], Dfs(tmp - (cur & (-cur))) + dis[num1][pos[val[cur & (-cur)]]]);
		cur -= cur & (-cur);
	}
	return f[sta];
}

int main()
{
	cin >> n >> k >> m;
	for(int i = 1; i <= 16; ++i) val[1 << (i - 1)] = i;
	for(int i = 1, x; i <= k; ++i) scanf( "%d", &x ), a[x] = 1;
	for(int i = 1; i <= m; ++i) scanf( "%d", &len[i] );
	for(int i = 0; i <= n; ++i)
	{
		b[i] = a[i] ^ a[i + 1];
		for(int j = 1; j <= m; ++j)
			if(i + len[j] <= n)
				Adde(i, i + len[j]);
	}
	int cnt = 0, sit = 0;
	for(int i = 0; i <= n; ++i)
		if(b[i]) Bfs(i, ++cnt), pos[cnt] = i;
	memset(f, -1, sizeof(f));
	sit = (1 << cnt) - 1;
	printf( "%d\n", Dfs(sit) );
	return 0;
}
/*
5 2 2
1 5
3 4
*/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值