【NOIP模拟赛】密码锁

题目描述

hzwer有一把密码锁,由N个开关组成。一开始的时候,所有开关都是关上的。当且仅当开关x1,x2,x3,…xk为开,其他开关为关时,密码锁才会打开。
他可以进行M种的操作,每种操作有一个size[i],表示,假如他选择了第i种的操作的话,他可以任意选择连续的size[i]个格子,把它们全部取反。(注意,由于黄金大神非常的神,所以操作次数可以无限>_<)
本来这是一个无关紧要的问题,但是,黄金大神不小心他的钱丢进去了,没有的钱他哪里能逃过被chenzeyu97 NTR的命运?>_< 于是,他为了虐爆czy,也为了去泡更多的妹子,决定打开这把锁。但是他那么神的人根本不屑这种”水题”。于是,他找到了你。
你的任务很简单,求出最少需要多少步才能打开密码锁,或者如果无解的话,请输出-1。

输入

第1行,三个正整数N,K,M,如题目所述。
第2行,K个正整数,表示开关x1,x2,x3..xk必须为开,保证x两两不同。
第三行,M个正整数,表示size[i],size[]可能有重复元素。

输出

输出答案,无解输出-1。

样例输入

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

样例输出

2

提示

【样例输入2】
3 2 1
1 2
3
【样例输出2】
-1
【数据规模】
对于50%的数据,1≤N≤20,1≤k≤5,1≤m≤3;
对于另外20%的数据,1≤N≤10000,1≤k≤5,1≤m≤30;
对于100%的数据,1≤N≤10000,1≤k≤10,1≤m≤100。

首先最终状态是1 1 1 0 1 1 1 1 1 0 0

差分后为     1 0 0 1 1 0 0 0 0 1 0

这个差分结果可以换成括号序列的思想来理解

四个1分别出现在1 4 5 10四个位置,这是一个半闭半开区间,也就是说[1,4) [5,10)这两个区间内的数都必须是1

怎么办。

我们先处理出每一段区间全部变成1所需要的最少操作数

初始时所有的位置都是0,所以我们的任务是让[1,4) [5,10)这两个区间内的数变成1,而且操作数最少

所以考虑所有让这两个内的数变成1的情况,算出每种情况的操作数,然后取最小

把[1,4) [5,10)这两个区间内的数变成1就是把这两个区间内的元素取反

我们发现,取反这两个区间和取反[1,6) [4,10)这两个区间是等价的,所以这些数可以随机两两组合来进行变换,每次变换的加起来就是这种方案的操作数

k辣么小很明显用到状态压缩

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
#define inf 1000000000
#define N 10005
#define M 2000005
#define T 45
queue<int>q;
int n,k,m,num[N],x[N],sz[N],a[N],cnt,dis[N],d[30][30],f[M];
bool vis[N],mark[M];
void bfs(int x){
    while(!q.empty())q.pop();
    memset(vis,0,sizeof(vis));
    q.push(x);
    vis[x]=1;dis[x]=0;
    while(!q.empty()){
        int now=q.front();q.pop();
        for(int i=1;i<=m;i++){
            if(now+sz[i]<=n&&(!vis[now+sz[i]])){
                vis[now+sz[i]]=1;
                dis[now+sz[i]]=dis[now]+1;
                q.push(now+sz[i]);
            }
            if(now-sz[i]>0&&(!vis[now-sz[i]])){
                vis[now-sz[i]]=1;
                dis[now-sz[i]]=dis[now]+1;
                q.push(now-sz[i]);
            }
        }
    }
    for(int i=1;i<=n;i++)
        if(num[i]){
            if(!vis[i])d[num[x]][num[i]]=inf;
            else d[num[x]][num[i]]=dis[i];
        }
}
int dp(int x){
    if(!x)return 0;
    if(mark[x])return f[x];
    mark[x]=1;f[x]=inf;
    int st=0;
    for(int i=1;i<=cnt;i++){
        if(x&(1<<(i-1))){
            if(!st)st=i;
            else{
                if(d[st][i]!=inf)
                f[x]=min(f[x],dp(x^(1<<(st-1))^(1<<(i-1)))+d[st][i]);
            }
        }
    }
    return f[x];
}
int main(){
    freopen("password.in","r",stdin);
    freopen("password.out","w",stdout);
    //freopen("Cola.txt","r",stdin);
    scanf("%d%d%d",&n,&k,&m);
    for(int i=1;i<=k;i++){scanf("%d",&x[i]);a[x[i]]=1;}
    for(int i=1;i<=m;i++)scanf("%d",&sz[i]);
    for(int i=n+1;i;i--)a[i]^=a[i-1];
    n++;
    for(int i=1;i<=n;i++){if(a[i])cnt++,num[i]=cnt;}
    for(int i=1;i<=n;i++)if(a[i])bfs(i);
    dp((1<<cnt)-1);
    if(f[(1<<cnt)-1]==inf)printf("-1");
    else printf("%d",f[(1<<cnt)-1]);
    return 0;
}

 

转载于:https://www.cnblogs.com/thmyl/p/6885248.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NOI(全国青少年信息学奥林匹克竞模拟的测试数据是指用于评测参选手的程序的输入和对应的输出。测试数据是非常重要的,因为它决定了参选手的程序能否正确地解决问题。 在NOI模拟中,测试数据具有以下特点: 1.充分覆盖:测试数据应涵盖各种可能的输入情况,包括边界条件和极端情况。通过提供不同的测试数据,可以考察选手对问题的全面理解和解决能力。 2.随机性和均衡性:为了公平起见,测试数据应该是随机生成的,而不是针对某个特定算法或解法设计的。同时,测试数据应该是均衡的,即各种情况的概率应该大致相等,以避免偏向某些解法。 3.合理性和可行性:测试数据应该是合理和可行的,即符合题目要求的输入数据,并且是选手能够通过编写程序来处理的。测试数据应该考虑到程序的限制和时间复杂度,以充分测试选手的编程能力。 NOI模拟的测试数据通常由经验丰富的考题组负责生成。他们会根据题目的要求和限制,设计出一组合理、充分、随机和均衡的测试数据,以确保参选手的程序在各种情况下都能正确运行,并且能通过性能测试。 总之,测试数据在NOI模拟中起到了至关重要的作用,它既考察了选手对问题的理解和解决能力,又提高了选手编程的技巧和效率。同时,合理和恰当的测试数据也是公平竞的保证,确保每个参选手有相同的机会和条件进行竞争。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值