zoukankan      html  css  js  c++  java
  • Luogu P3943 星空

    link

    题解

    题目可以看作有一个长度为 (n)(01) 串,有 (k) 位为 (1),其余为 (0)。每次操作相当于区间 (oplus 1),求变成全 (0) 串的最小操作次数。

    操作为区间修改,那么考虑对原 (01) 串进行差分。
    例如样例,原串是 (color{red}010001color{red}0),这里设第 (0) 位和第 (n+1) 位都为 (0),用红色数字表示。
    则差分之后是 (color{red}011001color{red}1),即设原串第 (i) 位为 (a_i),差分后新串第 (i) 位为 (d_i),则 (a_i=d_0oplus d_1oplus d_2oplus cdots oplus d_i)
    这个时候我们考虑,对原串 ([l,r]) 进行区间 (oplus1),其实就相当于令新串的 (d_lgets d_loplus1,d_{r+1}gets d_{r+1}oplus 1)

    变化有 (4) 种情况:

    1. (d_l=d_{r+1}=0),则相当于把这两位变成 (1),增加 (2)(1)
    2. (d_l=d_{r+1}=1),则相当于把这两位变成 (0),减少 (2)(0)
    3. (d_l=1,d_{r+1}=0),则相当于把 (l) 上的 (1) 移到 (r+1)(1) 数量不变。
    4. (d_l=0,d_{r+1}=1),则相当于把 (r+1) 上的 (1) 移到 (l)(1) 数量不变。

    那我们可以对任意 (i)(i+b_j) 连一条边权为 (1) 的无向边,则新串中 (d_lgets d_loplus 1,d_{r+1}gets d_{r+1}gets 1) 的最小操作次数为 (l)(r+1) 的最短路,记作 ( extit{dis}(l,r+1))。则对原串 ([l,r]) 进行区间 (oplus1) 的最小操作次数为 ( extit{dis}(l,r+1))。因为边权为 (1),所以求最短路跑 (n) 次 BFS 即可。
    容易得到,新串变为全 (0) 串是原串变为全 (0) 串的充要条件。

    考虑 (k) 很小,实际上新串中初始最多只有 (2k)(1),而其他的 (0) 我们根本就不用管,也就是说只用计这 (2k)(1)。则新串只有 (2^{2k}) 种状态,直接上状压 dp。
    (f(S)) 为新串状态为 (S) 时的最小操作次数,预处理出新串的初始状态 ( extit{st}),则 (f( extit{st})=0)
    显然每次转移,进行上面的第 (1,3,4) 种变化是没有意义的,所以只需要做第 (2) 种变化(即转移中,只转移第 (2) 种变化,实际上已经包括了其他 (3) 种)。
    (T=S-2^{i-1}-2^{j-1}),则 (f(T)=min {f(S)+ extit{dis}(i,j)})。答案即为 (f(0))
    同样的,我们在 BFS 的时候,只用从初始值为 (1) 的位开始搜,那么最多跑 (2k) 遍 BFS。
    时间复杂度为 (O(2knm+2^{2k}(2k)^2))

    实际上每次转移的时候,(i) 可以只取 (S)(1) 的最低位(无论如何都要转移这个最低位,那么可以先转移)。
    时间复杂度为 (O(2knm+2^{2k} (2k)))

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define MAX_N (40000 + 5)
    #define MAX_M (64 + 5)
    #define MAX_K (8 + 5)
    #define MAX_S ((1 << 16) + 5)
    using std::min;
    
    int n, m, k;
    int a[MAX_K * 2], len;
    int b[MAX_M];
    int dis[MAX_N];
    int g[MAX_K * 2][MAX_K * 2];
    int q[MAX_N], l, r;
    bool vis[MAX_N];
    int f[MAX_S];
    
    void BFS() {
    	int u, v;
    	for (int I = 1; I <= len; ++I) {
    		memset(dis, 0x3f, sizeof dis);
    		memset(vis, 0, sizeof vis);
    		l = r = 1;
    		dis[a[I]] = 0;
    		vis[a[I]] = 1;
    		q[1] = a[I];
    		while (l <= r) {
    			u = q[l++];
    			for (int i = 1; i <= m; ++i) {
    				v = u - b[i];
    				if (v >= 1 && !vis[v]) {
    					dis[v] = dis[u] + 1;
    					vis[v] = 1;
    					q[++r] = v;
    				}
    				v = u + b[i];
    				if (v <= n + 1 && !vis[v]) {
    					dis[v] = dis[u] + 1;
    					vis[v] = 1;
    					q[++r] = v;
    				}
    			}	
    		}
    		for (int i = 1; i <= len; ++i)
    			g[I][i] = dis[a[i]];
    	}
    }
    
    int main() {
    	scanf("%d%d%d", &n, &k, &m);
    	int pos;
    	for (int i = 1; i <= k; ++i) {
    		scanf("%d", &pos);
    		vis[pos] ^= 1;
    		vis[pos + 1] ^= 1;
    	}
    	for (int i = 1; i <= n + 1; ++i)
    		if (vis[i]) a[++len] = i;
    	for (int i = 1; i <= m; ++i)
    		scanf("%d", &b[i]);
    	BFS();
    	const int lim = (1 << len) - 1;
    	memset(f, 0x3f, sizeof f);
    	f[lim] = 0;
    	int tmp, T;
    	for (int S = lim; S; --S) {
    		tmp = S;
    		pos = 1;
    		while ((tmp & 1) == 0) {
    			++pos;
    			tmp >>= 1;
    		}
    		for (int i = pos + 1; i <= len; ++i) {
    			if ((S & 1 << i - 1) == 0) continue;
    			T = S ^ 1 << pos - 1 ^ 1 << i - 1;
    			f[T] = min(f[T], f[S] + g[pos][i]);
    		}
    	}
    	printf("%d", f[0]);
    	return 0;
    }
    
  • 相关阅读:
    [NOI2012] 美食节
    [NOI2008] 志愿者招募
    P3834 【模板】可持久化线段树 2(主席树)
    P3919 【模板】可持久化线段树 1(可持久化数组)
    P4168 [Violet]蒲公英
    轻重链剖分
    沉舟侧畔千帆过 病树前头万木春
    P2119 魔法阵 (0.1s 虐杀过程)
    两行虐杀儒略历
    CSP2020 S-2 爆零(日)记 (已完结)
  • 原文地址:https://www.cnblogs.com/kcn999/p/14048949.html
Copyright © 2011-2022 走看看