感谢 赞助 链接
Description
链接
给出一列长度为 有 个位置未被点亮的灯泡, 允许反转 种不同长度, 问最少反转多少次使所有灯泡亮起 .
Solution
异或差分:
作用: 用来对 数列进行多次区间反转
具体步骤:
- 在原来 数组的基础上建立 (异或差分) 数组 , (表异或)
- 对于一次操作 , 对 分别进行异或操作
- 操作完成之后即可得到处理后的数列
有了 前置芝士, 就可以来说说这道题目的解法了,
设原来的灯泡序列为 序列, 序列中 表示需要被点亮, 将其转换为 异或差分数组,
目的 是将 全部元素 变为 . (此时必定没有熄灭的灯)
消去 的方法只有异或, 即同时使 位置的 “1” 消去 .
为了避免浪费操作数, 要保证每次 区间反转 时的 位置至少有 个 “1”,
则操作时 位置的值分 两种情况
- “” 和 “”, 操作时不会影响 的总个数, 但却可以看做是 “” 挪动了位置.
- “” 和 “”, 操作时可以直接消去两个 “”.
2.操作 可以从本质上消去两个 “1”,
为了使当前局面可以满足使用 2.操作 的条件, 可以设想使用 1.操作 使 “1” 移动到对应位置,
此时又要 花费最小, 所以可以使用 处理出每个 “1” 到各个位置上的 最少操作次数
则消掉两个 ‘1’ 的代价即可直接使用 表示出来 .
接下来说怎么 计算答案 ,
熄灭的灯泡只有不超过 个, 则 差分数组 中 ‘1’ 不会超过 个,
则直接暴力 枚举哪两个灯泡匹配, 更新答案即可.
时间复杂度.
也可使用 状压dp ,
时间复杂度
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;
}