CodeForces 79 D.Password(SPFA+状压DP)

61 篇文章 0 订阅
40 篇文章 1 订阅

Description

n n 个开关,初始状态均为关闭状态,现在要通过若干操作打开其中的k个开关 x1,...,xk x 1 , . . . , x k ,每次操作可以选取一个起点后反转该起点及之后共 a1,...,al a 1 , . . . , a l 个开关的状态,问最少要几步操作可以达到要求,如果无解则输出 1 − 1

Input

第一行输入三个整数 n,k,l n , k , l 分别表示开关数,要打开的开关数以及操作种类数,之后输入 k k 个整数x1,...,xk表示要打开的开关编号,最后输入 l l 个整数a1,...,al表示一次操作可以打开的连续开关数

(1n1000,1k10,1l100,1x1<x2<...<xkn) ( 1 ≤ n ≤ 1000 , 1 ≤ k ≤ 10 , 1 ≤ l ≤ 100 , 1 ≤ x 1 < x 2 < . . . < x k ≤ n )

Output

输出最少操作数,如果无解则输出 1 − 1

Sample Input

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

Sample Output

2

Solution

定义状态: yi=1 y i = 1 表示第 i i 个开关状态与第i+1个开关状态相同, yi=0 y i = 0 表示不同, 0in 0 ≤ i ≤ n (不妨令 x0=xn+1=0 x 0 = x n + 1 = 0

那么初始状态 y0=...=yn=0 y 0 = . . . = y n = 0 ,目标状态是 yxi1=yxi=1,1ik y x i − 1 = y x i = 1 , 1 ≤ i ≤ k ,假设一次操作反转了从 i i 开始的a个开关,那么该步操作后, y y 中只有yi1 yi+a1 y i + a − 1 状态改变,以此为关系建边,边权为 1 1 表示该步操作的代价,由于目标状态中y 1 1 的位置至多2k个,故可以从每点开始求一遍最短路进而得到将 yi=yj=0 y i = y j = 0 同时变成 1 1 的最小代价cost[i][j](最短路一方面保证操作数最少,另一方面保证了这一系列操作后只有这两个位置的值改变)

得到代价函数之后就可以状压了,把 y y 中期望变成1的这 m m 个位置看作m 01 01 状压, dp[S] d p [ S ] 表示这 m m 个位置中1的状态为 S S 时所需的最少操作步数,那么dp[2m1]即为答案,每次转移只需选取状态 S S 中为1的两个位置 i,j i , j ,进而有转移 dp[S]=min(dp[S],dp[S2i2j]+cost[i][j]) d p [ S ] = m i n ( d p [ S ] , d p [ S − 2 i − 2 j ] + c o s t [ i ] [ j ] )

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const int INF=0x3f3f3f3f,maxn=10005;
int n,k,l,x[maxn],a[maxn],dis[maxn],vis[maxn],cost[22][22],id[maxn],dp[1<<22];
vector<int>g[maxn];
void spfa(int s)
{
    memset(vis,0,sizeof(vis));
    queue<int>que;
    for(int i=0;i<maxn;i++)dis[i]=INF;
    dis[s]=0,vis[s]=1;
    que.push(s);
    while(!que.empty())
    {
        int u=que.front();que.pop();
        vis[u]=0;
        for(int i=0;i<g[u].size();i++)
        {
            int v=g[u][i];
            if(dis[v]>dis[u]+1)
            {
                dis[v]=dis[u]+1;
                if(!vis[v])vis[v]=1,que.push(v);
            }
        }
    }
}
void add(int u,int v)
{
    g[u].push_back(v),g[v].push_back(u);
}
int main()
{
    scanf("%d%d%d",&n,&k,&l);
    memset(x,0,sizeof(x));
    for(int i=1;i<=k;i++)
    {
        int temp;
        scanf("%d",&temp);
        x[temp]=1;
    }
    for(int i=0;i<=n;i++)
        if(x[i]!=x[i+1])x[i]=1;
        else x[i]=0;
    for(int i=1;i<=l;i++)scanf("%d",&a[i]);
    for(int i=0;i<=n;i++)
        for(int j=1;j<=l;j++)
        {
            if(i-a[j]>=0&&x[i-a[j]]==0)add(i-a[j],i);
            if(i+a[j]<=n)add(i,i+a[j]);
        }
    k=0;
    for(int i=0;i<=n;i++)
        if(x[i])id[i]=k++;
    memset(cost,INF,sizeof(cost));
    for(int i=0;i<=n;i++)
        if(x[i])
        {
            spfa(i);
            for(int j=0;j<=n;j++)
                if(x[j])cost[id[i]][id[j]]=dis[j];
        }
    memset(dp,INF,sizeof(dp));
    dp[0]=0;
    int K=1<<k;
    for(int i=1;i<K;i++)
    {
        int x,y;
        for(x=0;x<k;x++)
            if((i>>x)&1)break;
        for(y=x+1;y<k;y++)
            if((i>>y)&1)
                dp[i]=min(dp[i],dp[i^(1<<x)^(1<<y)]+cost[x][y]);
    }
    if(dp[K-1]==INF)printf("-1\n");
    else printf("%d\n",dp[K-1]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值