P3943 星空

15 篇文章 0 订阅
8 篇文章 0 订阅

感谢 L B X LBX LBX 赞助 链接

星 空 星空


Description

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


Solution

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

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

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

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

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

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

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

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

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


接下来说怎么 计算答案 ,

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

也可使用 状压dp ,

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


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\n", dp[(1<<cnt)-1]);
        return 0;
}

这里有一道类似的题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值