P3943 星空

感谢 LBXLBX 赞助 链接

星空


Description

链接
给出一列长度为 NNKK 个位置未被点亮的灯泡, 允许反转 MM 种不同长度, 问最少反转多少次使所有灯泡亮起 .


Solution

异或差分:
作用: 用来对 0,10, 1 数列进行多次区间反转
具体步骤:

  1. 在原来 AA 数组的基础上建立 BB (异或差分) 数组 , Bi=Ai1Ai   .B_i = A_{i-1}igoplus A_i .(igoplus表异或)
  2. 对于一次操作 [L,R][L, R], 对 BL,BR+1B_L, B_{R+1} 分别进行异或操作
  3. 操作完成之后即可O(N)mathcal{O(N)}得到处理后的数列

有了 前置芝士, 就可以来说说这道题目的解法了,

设原来的灯泡序列为 0,10, 1 序列, 序列中 11 表示需要被点亮, 将其转换为 异或差分数组,

目的 是将 全部元素 变为 00 . (此时必定没有熄灭的灯)

消去 11 的方法只有异或, 即同时使 l,r+1l, r+1 位置的 “1” 消去 .

为了避免浪费操作数, 要保证每次 区间反转 时的 l  r+1l 或 r+1 位置至少有 11 个 “1”,
则操作时 l  r+1l 和 r+1 位置的值分 两种情况

  1. 11” 和 “00”, 操作时不会影响 11 的总个数, 但却可以看做是 “11” 挪动了位置.
  2. 11” 和 “11”, 操作时可以直接消去两个 “11”.

2.操作 可以从本质上消去两个 “1”,
为了使当前局面可以满足使用 2.操作 的条件, 可以设想使用 1.操作 使 “1” 移动到对应位置,
此时又要 花费最小, 所以可以使用 SPFAmathcal{SPFA} 处理出每个 “1” 到各个位置上的 最少操作次数
则消掉两个 ‘1’ 的代价即可直接使用 Dis[i,j]Dis[i, j] 表示出来 .


接下来说怎么 计算答案 ,

熄灭的灯泡只有不超过 88 个, 则 差分数组 中 ‘1’ 不会超过 1616 个,
则直接暴力 DFSDFS 枚举哪两个灯泡匹配, 更新答案即可.
时间复杂度O(91618)O(9^{16}*18).

也可使用 状压dp ,

时间复杂度 O(216182)O(2^{16}*18^2)


Code

#include<bits/stdc++.h>
#define reg register

int read(){
        char c;
        int s = 0, flag = 1;
        while((c=getchar()) && !isdigit(c))
                if(c == '-'){ flag = -1, c = getchar(); break ; }
        while(isdigit(c)) s = s*10 + c-'0', c = getchar();
        return s * flag;
}

int N;
int K;
int M;
int cnt;
int A[40004];
int B[40004];
int pos[205];
int opt[205];
int Dis[18][40004];
int dp[(1<<18) + 233];

std::bitset <40004> Used;

void SPFA(const int &x){
        std::queue <int> Q;
        Q.push(pos[x]);
        Used.set(pos[x], 1), Dis[x][pos[x]] = 0;
        while(!Q.empty()){
                int ft = Q.front(); Q.pop();
                Used.set(ft, 0); int tmp;
                for(reg int i = 1; i <= M; i ++){
                        tmp = ft - opt[i]; 
                        if(tmp > 1 && Dis[x][tmp] > Dis[x][ft] + 1){ 
                                Dis[x][tmp] = Dis[x][ft] + 1; 
                                if(!Used.test(tmp)) Q.push(tmp), Used.set(tmp, 1); 
                        }
                        tmp = ft + opt[i];
                        if(tmp <= N+1 && Dis[x][tmp] > Dis[x][ft] + 1){
                                Dis[x][tmp] = Dis[x][ft] + 1;
                                if(!Used.test(tmp)) Q.push(tmp), Used.set(tmp, 1); 
                        }
                }
        }
}

int main(){
        freopen("starlit.in", "r", stdin);
        freopen("starlit.out", "w", stdout);
        N = read(), K = read(), M = read();
        for(reg int i = 1; i <= K; i ++) A[read()] = 1;
        for(reg int i = 1; i <= M; i ++) opt[i] = read();
        for(reg int i = 1; i <= N+1; i ++){
                B[i] = A[i]^A[i-1];
                if(B[i]) pos[++ cnt] = i;
        }
        memset(Dis, 0x3f, sizeof Dis);
        for(reg int i = 1; i <= cnt; i ++) SPFA(i);
        memset(dp, 0x3f, sizeof dp);
        dp[0] = 0;
        for(reg int i = 0; i <= (1<<cnt)-1; i ++)
                for(reg int l = 1; l <= cnt; l ++)
                        for(reg int r = l+1; r <= cnt; r ++){
                                if(!((i>>cnt-l)&1) || !((i>>cnt-r)&1)) continue ;
                                int tmp = i ^ (1<<cnt-l) ^ (1<<cnt-r);
                                dp[i] = std::min(dp[i], dp[tmp]+Dis[l][pos[r]]);
                        }
        printf("%d
", dp[(1<<cnt)-1]);
        return 0;
}

这里有一道类似的题

原文地址:https://www.cnblogs.com/zbr162/p/11822635.html