题面:
题解:
很好的思维题,考察数学抽象和问题转化能力。
考虑到区间最长为$40000$,若暴力翻转,一次复杂度为$O(n)$,显然不可承受,考虑将区间操作转化为单点操作,所以我们可想到差分,因为一次翻转为取$xor$,所以我们定义差分为$b[i]=a[i]\ xor\ a[i+1]$。
差分后数组变为一个$01$串,包含不超过$2k$个零。
问题转化(转化一)为:给定一个$01$串,有不超过$2k$个零,每次操作时,从$m$种距离中选一个,把序列上相距为该距离的两个数取反,问使整个串为$0$的最少操作数。
观察该问题,我们可能会有两种操作,
1.一个$1$,一个$0$,相当于移动。
2.两个$0$,相当于消去。
这样,我们可以继续把问题转化(转化二)为:一张图,有$2k$个物品在不同起点,每次操作时,从$m$种距离中选一个,把该物品移动,当两物品相遇时,它们消去,求最少操作数。
我们发现,图上一共有$n$个节点,每个点有$m$条边,于是我们可以用$spfa$预处理出两两间的最短路,复杂度为$O(nmk)$。
考虑继续转化(转化三)为:2k个物品,选择其中两个可以消去,分别有不同的代价。
显然可用状压处理,但注意要写复杂度为$O(k*2^{2k})$的,防止被卡。附上大佬博客。
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
using namespace std;
#define R register
#define ll long long
inline ll read(){
ll aa=0;R int bb=1;char cc=getchar();
while(cc<'0'||cc>'9')
{if(cc=='-')bb=-1;cc=getchar();}
while(cc>='0'&&cc<='9')
{aa=(aa<<1)+(aa<<3)+(cc^48);cc=getchar();}
return aa*bb;
}
const int N=40003;
const int M=18;
int n,k,m,a[N],b[N],tt,pos[M],dp[1<<M];
int ds[N],dis[M][M];
queue<int>qi;
inline void spfa(int st)
{
memset(ds,0,sizeof(ds));
int x=pos[st];ds[x]=1;qi.push(x);
while(!qi.empty()){
int u=qi.front();qi.pop();
for(R int i=1;i<=m;++i){
if(u+b[i]<=n&&!ds[u+b[i]]){
ds[u+b[i]]=ds[u]+1;
qi.push(u+b[i]);
}
if(u-b[i]>=0&&!ds[u-b[i]]){
ds[u-b[i]]=ds[u]+1;
qi.push(u-b[i]);
}
}
}
for(R int i=1;i<=tt;++i)dis[st][i]=ds[pos[i]]-1;
}
int main()
{
n=read();k=read();m=read();
for(R int i=1,x;i<=k;++i){x=read();a[x]^=1;}
for(R int i=0;i<=n;++i){
a[i]^=a[i+1];
if(a[i])pos[++tt]=i;
}
for(R int i=1;i<=m;++i)b[i]=read();
for(R int i=1;i<=tt;++i)spfa(i);
const int lim=(1<<tt)-1;
memset(dp,0x3f,sizeof(dp)); dp[0]=0;
for(R int i=0;i<lim;++i)
for(R int j=1;j<=tt;++j){
if((1<<(j-1))&i)continue;
for(R int q=j+1,x;q<=tt;++q){
if(((1<<(q-1))&i)||dis[j][q]==-1)continue;
x=(i|(1<<(q-1))|(1<<(j-1)));
dp[x]=min(dp[x],dp[i]+dis[j][q]);
}
break;//保证复杂度,只需处理第一个位置,防止重复。
}
printf("%d\n",dp[lim]);
return 0;
}