BZOJ3003 LED题解(状压DP+最短路+差分)

题目:BZOJ3003.
题目大意:给定一个长度为 n n n序列 a i a_i ai与给定 m m m种操作, a i a_i ai初始全为 0 0 0,每种操作给定一个长度,表示可以对这个长度的区间取反.现在要你用一定次数的操作把整个序列变成只有 k k k个位置为 1 1 1,其余全为 0 0 0,求最少操作数.
数据组数 T ≤ 10 T\leq 10 T10 1 ≤ n ≤ 1 0 4 , 1 ≤ m ≤ 100 , 1 ≤ k ≤ 10 1\leq n\leq 10^4,1\leq m\leq 100,1\leq k\leq 10 1n104,1m100,1k10.

对于这种区间染色的题,我们发现区间染色的操作非常麻烦,所以考虑把操作和序列都进行异或差分,现在操作变成相隔一定长度的两个位置取反,同时最终的序列变成了最多 2 k 2k 2k个位置为 1 1 1.

之后考虑把问题变为从终态到初态的最少步数,然后状压DP.设 f [ S ] f[S] f[S]表示集合 S S S中的位置为 1 1 1其它位置为 0 0 0需要的最少步数,那么可以列出转移:
f [ S ] = min ⁡ i , j ∉ S { f [ S ∪ { i , j } ] + g ( i , j ) } f[S]=\min_{i,j\notin S}\{f[S\cup\{i,j\}]+g(i,j)\} f[S]=i,j/Smin{f[S{i,j}]+g(i,j)}

其中 g ( i , j ) g(i,j) g(i,j)表示把位置 i i i和位置 j j j同时消掉需要的最少步数,但是这个东西并不好求.

考虑若当前用长度为 l e n 1 len_1 len1的操作消掉了位置 i i i,则位置 i + l e n 1 i+len_1 i+len1会变为 1 1 1,之后用长度为 l e n 2 len_2 len2的操作消掉位置 i + l e n 1 i+len_1 i+len1,则位置 i + l e n 1 i+len_1 i+len1会变为 0 0 0 i + l e n 1 + l e n 2 i+len_1+len_2 i+len1+len2变为 1 1 1

我们会发现上述过程类似于一个最短路,对于每一个位置 i i i和每一个区间长度 j j j,则将位置 i i i i + j , i − j i+j,i-j i+j,ij两个位置连接起来,跑个最短路就完事了.

一个小优化,容易发现每次转移的时候位置 ( i , j ) (i,j) (i,j)中有一个位置是可以钦定而不枚举的,这样就可以少 O ( k ) O(k) O(k)的时间复杂度了.

时间复杂度 O ( T ( n m + 2 2 k k ) ) O(T(nm+2^{2k}k)) O(T(nm+22kk)).

代码如下:

#include<bits/stdc++.h>
using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=10000,M=100,K=10,INF=(1<<30)-1;

int num[(1<<K*2)+9];

void Get_num(){
  for (int i=0;i<=K*2;++i) num[1<<i]=i;
}

int n,m,sk,a[N+9],len[M+9];
int p[K*2+9],cp;

void Get_p(){
  cp=0;
  for (int i=1;i<=n;++i)
    if (a[i]) p[++cp]=i;
}

struct side{
  int y,next;
}e[N*M*2+9];
int lin[N+9],cs;

void Ins(int x,int y){e[++cs].y=y;e[cs].next=lin[x];lin[x]=cs;}
void Ins2(int x,int y){Ins(x,y);Ins(y,x);}

void Get_graph(){
  for (int i=1;i<=n;++i) lin[i]=0;
  cs=0;
  for (int i=1;i<=m;++i)
    for (int j=1;j<=n;++j){
      if (j-len[i]>=1) Ins(j,j-len[i]);
      if (j+len[i]<=n) Ins(j,j+len[i]);
    }
}

queue<int>q;
int dis[K*2+9][N+9],vis[N+9];

void Bfs_dis(int id,int st){
  for (int i=1;i<=n;++i) dis[id][i]=INF,vis[i]=0;
  dis[id][st]=0;vis[st]=1;q.push(st);
  for (;!q.empty();){
  	int t=q.front();q.pop();
  	for (int i=lin[t];i;i=e[i].next)
  	  if (!vis[e[i].y]){
  	  	dis[id][e[i].y]=dis[id][t]+1;
  	  	vis[e[i].y]=1;
  	  	q.push(e[i].y);
  	  }
  }
  dis[id][st]=INF;
}

int dp[(1<<K*2)+9];

void Get_dp(){
  for (int g=0;g<1<<cp;++g) dp[g]=INF;
  dp[(1<<cp)-1]=0;
  for (int g=(1<<cp)-1;g>0;--g){
  	if (dp[g]==INF) continue;
  	int t0=g&-g;
  	if (g-t0==0) continue;
  	for (int i=g-t0;i;i-=i&-i){
  	  int t1=i&-i;
  	  dp[g^t0^t1]=min(dp[g^t0^t1],dp[g]+dis[num[t0]+1][p[num[t1]+1]]);
    }
  }
}

Abigail start(){
  Get_num();
}

Abigail into(){
  scanf("%d%d%d",&n,&sk,&m);++n;
  for (int i=1;i<=n;++i) a[i]=vis[i]=0;
  for (int i=1;i<=sk;++i){
  	int x;
	scanf("%d",&x);
	if (vis[x]) continue;
	vis[x]=1;
	a[x]^=1,a[x+1]^=1;
  }
  for (int i=1;i<=m;++i)
    scanf("%d",&len[i]);
}

Abigail work(){
  Get_p();
  Get_graph();
  for (int i=1;i<=cp;++i) Bfs_dis(i,p[i]);
  Get_dp();
}

Abigail outo(){
  printf("%d\n",dp[0]==INF?-1:dp[0]);
}

int main(){
  int T;
  start();
  scanf("%d",&T);
  while (T--){
    into();
    work();
    outo();
  }
  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值