这道题思维难度非常高,有很多处理的小技巧,并且代码也有很多细节 ;
第一步
这道题是一种序列的区间操作,我们都知道,区间操作比较麻烦,所以我们要想办法将区间操作转换成单点修改;
这时,我们想到了差分,假如我们对一个序列进行操作,这时一个序列里的相对状态不会变只有两端改变,换句话说就是这个序列的差分并不会发生改变,只有两端的差分数组会发生变化。但这里的差分并不是差
而是异或;对一个区间[l,r]进行修改操作就是将差分数组c[l],c[r+1]取反,最后我们的目标就是要将差分数组全部变成0;
但这里就有一个细节:差分数组全部变成0的意思就是所有的状态都是一样的,但有可能全部亮或者全不亮;这里我们要假设第0个灯泡是亮的;然后差分出来才是合法的全亮状态;
还有一个细节:差分数组要开到n+1来保证至少有偶数个数的1,这样才能消去 ;
所以我们就这样完成了第一步,将区间修改转化成单点修改;
第二步
处理出来差分数组之后,我们就要将这个序列全部变为0 ;
有两种情况;一个0一个1,这样操作之后1的个数没有变 ;
两个都是1,这种情况就是减少两个1
但大多数情况并不能一次操作就完成,因为只能操作m种长度 ;
所以实际情况是1先和一些0交换,然后两个1一起消去 ;
所以对所有1跑一边BFS处理出到与其他1消去所需要的步数;
bfsO(n)就可以完成;
第三步
处理完每个1与其他1消去之后,就要考虑如何选择使其代价最少 ;
我们可以注意到其实原来的1很少不超过16个,于是我们可以开始考虑状压DP ;
我们反着DP;
dp[i]表示状态为i到状态为0所需要的最小步数 ;
初始化dp[0]=0 ;
然后倒着枚举点亮哪两个灯,(因为倒着来是点亮,正着来就是消去两个灯)
然后点亮完了的状态就是i|(1<<x1)|(1<<x2)
然后就可以得到DP转移方程:
int now=(i|(1<<fir))|(1<<j) ;
dp[now]=min(dp[now],dp[i]+getdis(fir+1,j+1)) ;
最后再输出dp[最初的状态] ;
到此,这道题就顺利的解决了;
具体细节请看代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 40101 ;
int n,k,m ;
int a[maxn],c[maxn],dis[20][maxn] ;
int len[maxn],sta[maxn],top=0 ;
int dp[1<<17] ;
bool vis[maxn] ;
inline void read(int &x)
{
int f=1 ;x=0 ;char s=getchar() ;
while(s<'0'||s>'9'){if(s=='-')f=-1 ;s=getchar() ; }
while(s>='0'&&s<='9'){x=x*10+s-'0' ;s=getchar() ; }
x*=f ;
}
void bfs(int s,int *dis)
{
memset(vis,0,sizeof(vis)) ;
queue<int>q ;
dis[s]=0 ;vis[s]=1 ;q.push(s) ;
while(q.size())
{
int x=q.front() ;q.pop() ;
for(int i=1;i<=m;++i)
{
int y=x-len[i] ;
if(y>0&&!vis[y])
{
vis[y]=1;
dis[y]=dis[x]+1 ;
q.push(y) ;
}
y=x+len[i] ;
if(y<=n+1&&!vis[y])
{
vis[y]=1 ;
dis[y]=dis[x]+1 ;
q.push(y) ;
}
}
}
}
int getdis(int x,int y)
{
return dis[x][sta[y]] ;
}
int main()
{
//freopen("starlit.in","r",stdin) ;
//freopen("starlit.out","w",stdout) ;
read(n) ;read(k) ;read(m) ;
memset(a,1,sizeof(a)) ;
for(int i=1;i<=k;++i)
{
int x ;read(x) ;
a[x]=0 ;
}
for(int i=1;i<=n+1;++i) c[i]=a[i]^a[i-1] ;
for(int i=1;i<=m;++i) read(len[i]) ;
memset(dis,0x3f,sizeof(dis)) ;
memset(dp,0x3f,sizeof(dp)) ;
for(int i=1;i<=n+1;++i)
if(c[i]) sta[++top]=i,bfs(i,dis[top]) ;
dp[0]=0 ;
for(int i=0,t=(1<<top)-1;i<t;++i)
{
int fir=-1 ;
for(int j=0;j<top;++j) if(!((i>>j)&1)) {fir=j ;break ; }
for(int j=fir+1;j<top;++j)
{
if(!((i>>j)&1))
{
int now=(i|(1<<fir))|(1<<j) ;
dp[now]=min(dp[now],dp[i]+getdis(fir+1,j+1)) ;
}
}
}
printf("%d",dp[(1<<top)-1]) ;
return 0 ;
}