1. 程式人生 > >[模擬考試題][神題]Star Sky[狀壓DP][BFS][差分]

[模擬考試題][神題]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
*/