BZOJ3003 差分+状压

本文探讨了一种涉及01序列区间取反操作的问题,并提出了一种高效的算法解决方案。通过对序列进行差分处理,结合背包算法优化操作成本,并利用状态压缩技巧减少计算复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给定一个长度为n的01序列以及L个整数a[i]。每次可以选择连续的长度为a[i]的一段取反,给出末状态。求最少操作的次数。

首先,这类选择一段区间操作的题可以把序列差分。于是我们把给出的末状态差分,然后考虑把每次操作也差分,于是每次操作就是选择两个位置差为a[i]的数对取反。

然后我们对所有的a[i]跑一遍背包(即取正也取负),这样就得到了把任意位置差的两个点取反的最小代价。

接着考虑把所有的位置给状压,然后每次取两个数对。但是这样是2nn2的,不是很优雅。然后发现我们每次可以固定取一个 lowbit,这样一定是可以涵盖到所有的方案

#include<bits/stdc++.h>
#define inf ~0u>>2
using namespace std;
const int N=2e4+5;
inline int read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x;
}
int T,n,k,l; 
int a[N],c[N],pos[N],f[N],g[(1<<22)];
int cnt; 
queue<int>q;
int main(){
#ifdef Devil_Gary
    freopen("in.txt","r",stdin);
#endif
    T=read();
    while(T--){
        cnt=0,n=read(),k=read(),l=read();
        for(int i=1;i<=k;i++) a[i]=read();
        sort(a+1,a+k+1);
        k=unique(a+1,a+k+1)-(a+1);
        a[0]=-1,a[k+1]=n+2;
        for(int i=1;i<=k;i++) {
            if(a[i]!=a[i-1]+1) pos[cnt++]=a[i];
            if(a[i]!=a[i+1]-1) pos[cnt++]=a[i]+1; 
        }
        sort(pos,pos+cnt);
        for(int i=1;i<=n;i++) f[i]=inf;
        for(int i=1;i<=l;i++) c[i]=read();
        for(int i=1;i<=l;i++) f[c[i]]=1,q.push(c[i]);
        while(!q.empty()){
            int o=q.front();q.pop();
            for(int i=1;i<=l;i++){
                if(o+c[i]<=n&&f[o+c[i]]==inf){
                    f[o+c[i]]=f[o]+1;
                    q.push(o+c[i]);
                }
                if(o-c[i]>=1&&f[o-c[i]]==inf){
                    f[o-c[i]]=f[o]+1;
                    q.push(o-c[i]);
                }
            } 
        }
        for(int s=1,o;s<(1<<cnt);s++){
            o=0,g[s]=inf;
            for(int i=0;i<cnt;i++)
                if(s&(1<<i)){
                    o=i;break;
                }
            for(int i=o+1;i<cnt;i++)
                if(s&(1<<i))
                    g[s]=min(g[s],g[s^(1<<i)^(1<<o)]+f[pos[i]-pos[o]]); 
        }
        printf("%d\n",g[(1<<cnt)-1]==inf?-1:g[(1<<cnt)-1]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值