DTOJ2548 翻转硬币

58 篇文章 0 订阅

题目

题目描述

n n n枚硬币正面朝上摆成一排,给定 a 1 , a 2 , ⋯   , a m a_1,a_2,\cdots,a_m a1,a2,,am,每次操作可以翻转连续 a i a_i ai个硬币。要求经过最少次数的操作,使得仅第 x 1 , x 2 , ⋯   , x k x_1,x_2,\cdots,x_k x1,x2,,xk枚硬币反面朝上,输出最少次数

输入格式

第一行三个整数 n , k , m n,k,m n,k,m
第二行 k k k个整数表示需要反面朝上的硬币位置,从 1 1 1编号
第三行 m m m个整数表示 a 1 , a 2 , ⋯   , a m a_1,a_2,\cdots,a_m a1,a2,,am

输出格式

一个整数表示答案,若无解,则输出 − 1 -1 1

样例

样例输入

10 8 2
1 2 3 5 6 7 8 9
3 5

样例输出

2

数据范围与提示

对于 30 % 30\% 30%的数据, n , m ⩽ 10 n,m\leqslant 10 n,m10
对于 60 % 60\% 60%的数据, m ⩽ 20 m\leqslant 20 m20
对于 100 % 100\% 100%的数据, 1 ⩽ n ⩽ 10000 , 1 ⩽ k ⩽ 10 , 1 ⩽ m ⩽ 100 , 1 ⩽ a [ i ] ⩽ n 1\leqslant n\leqslant 10000,1\leqslant k\leqslant 10,1\leqslant m\leqslant100,1\leqslant a[i]\leqslant n 1n10000,1k10,1m100,1a[i]n

题解

因为每次翻转改变的是相邻硬币相对的状态
所以我们用 d i d_i di表示相邻硬币相对的状态,即 0 0 0表示状态相同, 1 1 1表示状态不同
现在,假设我们翻转 [ x + 1 , x + a i ) [x+1,x+a_i) [x+1,x+ai),那么,我们只会影响 d x d_x dx b x + a i b_{x+a_i} bx+ai,有三种情况:

  1. d x = d x + a i = 0 d_{x}=d_{x+a_i}=0 dx=dx+ai=0:这个翻转没啥用
  2. d x = d x + a i = 1 d_{x}=d_{x+a_i}=1 dx=dx+ai=1:两个都变成 0 0 0
  3. d x = 0 , d x + a i = 1 d_{x}=0,d_{x+a_i}=1 dx=0,dx+ai=1:就相当于是把 x + a i x+a_i x+ai移到了 x x x

所以我们可以先预处理出每个 d i = 1 d_i=1 di=1 i i i到其他 d j = 1 d_j=1 dj=1 j j j的距离 g i , j g_{i,j} gi,j
然后我们就可以状压状压 d p dp dp
我们用 f s f_s fs为到达状态 s s s所需最少次数
我们假设 s s s中为 1 1 1的位最大是 k k k(即最大的满足 i & 2 k = 1 i\&2^k=1 i&2k=1 k k k),那么 f s − 2 j − 2 k = m i n { f s + g j , k } f_{s-2^j-2^k}=min\{f_{s}+g_{j,k}\} fs2j2k=min{fs+gj,k},其中, j j j s s s中为 1 1 1的位,且 j ≠ k j\neq k j=k
附上代码:

#include<cstring>
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
int n,m,k,nd=-1,c[10010],a[110],d[30],f[1048580],g[30][30],dis[10010];
queue<int> q;
void bfs(int x)
{
	dis[x]=0,q.push(x);
	while(!q.empty()){
		x=q.front(),q.pop();
		for(int i=1;i<=m;i++){
			if(x-a[i]>=0&&dis[x]+1<dis[x-a[i]]) dis[x-a[i]]=dis[x]+1,q.push(x-a[i]);
			if(x+a[i]<=n&&dis[x]+1<dis[x+a[i]]) dis[x+a[i]]=dis[x]+1,q.push(x+a[i]);
		}
	}
}
int main()
{
//	freopen("coin.in","r",stdin);
//	freopen("coin.out","w",stdout);
	scanf("%d%d%d",&n,&k,&m);
	for(int i=1,j;i<=k;i++) scanf("%d",&j),c[j]=1;
	for(int i=1;i<=m;i++) scanf("%d",&a[i]);
	for(int i=0;i<=n;i++) if(c[i]!=c[i+1]) d[++nd]=i;
	for(int i=0;i<=1048576;i++) f[i]=100000000;
	for(int i=0;i<=nd;i++) for(int j=0;j<=nd;j++) g[i][j]=100000000;
	for(int i=0;i<=nd;i++){
		for(int j=0;j<=n;j++) dis[j]=100000000;
		bfs(d[i]);
		for(int j=0;j<=nd;j++) g[j][i]=g[i][j]=min(g[i][j],dis[d[j]]);
	}
	f[(1<<nd+1)-1]=0;
	for(int i=(1<<nd+1)-1,k;i;i--){
		for(k=nd;k>=0;--k) if(i&(1<<k)) break;
		for(int j=0;j<=nd;j++) if((i&(1<<j))&&j!=k) f[i-(1<<j)-(1<<k)]=min(f[i-(1<<j)-(1<<k)],f[i]+g[j][k]);
	}
	if(f[0]<100000000) printf("%d",f[0]);
	else printf("-1");
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值