码迷,mamicode.com
首页 > 其他好文 > 详细

10-2国庆节第四场模拟题题解

时间:2018-10-03 22:15:10      阅读:155      评论:0      收藏:0      [点我收藏+]

标签:数位   位置   左右   广搜   实现   思考   一个   查询   考试   

T1 电灯 (light)

  Description

? 有 n 个灯泡排成一列。每个灯泡可能是点亮或熄灭的。有一台操控灯泡的机器,每一次可以选择一段区间,让这段区间中熄灭的灯泡全部点亮,亮着的灯泡全部熄灭。但由于机器已经老化,仅能再使用一次了。

? 你可以认为点亮的灯泡与熄灭的灯泡交替排列的样子(下面称这样的灯泡列为交替列)很好看。现在,你希望珍惜最后一次操控灯泡的机会,使得操控后这列灯泡中最长的交替列尽可能地长。

? 例如,这列灯泡若原本如下所示(○ 表示点亮的灯泡,● 为熄灭的灯泡):

? ○ ○ ● ● ○ ● ○ ○ ○ ●

? 如果选择第 4 个到第 7 个灯泡,则会变成如下的形式:

? ○ ○ ● ○ ● ○ ● ○ ○ ●

? 此时,最长的交替列为第 2 个到第 8 个灯泡,长度为 7。

? 而如果仅选择第 88 个灯泡,则会变成如下的形式:

? ○ ○ ● ● ○ ● ○ ● ○ ●

? 此时,最长的交替列为第 4个到第 10 个灯泡,长度也为 7。

? 可以发现,此例中没有方法能使得最长交替列长度大于 77,则 77 即为答案。

? Input

? 输入文件第一行一个正整数 n,表示灯泡的数量。第二行包含 n 个数字,每个数字均为 0 或 1,依次代表序列中每个灯泡的初始状态。1 代表点亮,0 代表熄灭。

? Output

? 输出一个整数,表示所有能得到的灯泡列中最长的交替列的长度。

第一道题的意思就是初始有一个01串,约定01相间的区间是美的,并且我们仅有一次机会将某个区间内的所有值翻转,即0变成1,1变成0。问题是找出翻转后的最长美的区间的长度。

eolv大佬在讲题的时候是这么说的:对于类似这种给你一个01串,然后让你关于01相间这个要求求一些东西的题,再没读完题的时候就应该想到要把所有偶数位(或者奇数位)的01数字翻转,这样的话,再去求关于01相见的东西时候,就是可以转化成求相同的区间了。

仔细想一想确实这样,记得一道题:棋盘制作,也是可以通过上述方法将问题转化,使之变成更加好求的问题。

这道题的思路:在输入过程中,将所有偶数位上的数字全部翻转,在对转化后的序列进行操作。

? 先求出这个序列中有多少个连续相同的小区间,只需要统计这些小区间的长度,再去枚举每一个区间。

? 像这样:110001100011 当枚举到中间1的小区间时,我们将这一整个小区间翻转,那么可以得到的答案就是t[now-1]+t[now]+t[now+1],也就是反转中间区间,会将该区间前面的区间和后面的区间与反转之后的当前区间共同组成一个新的比较大的区间,再通过预处理出的区间长度,可以做到O(1)地查询当前答案并更新。

? 这样 ,整体的复杂度就是O(n)的。

之后按照这个思路去实现了一遍,忽然发现在考场上感觉挺难的题,立马变水了。

在考场上还是应该尽力沉下心来读题,沉下心来思考。

code:

#include<iostream>
#include<cstdio>
using namespace std;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){
        if(ch==‘-‘)f=-1;
        ch=getchar();
    }
    while(ch>=‘0‘&&ch<=‘9‘){
        sum=(sum<<1)+(sum<<3)+ch-‘0‘;
        ch=getchar();
    }
    return sum*f;
}
int n,ans,tot,flag;
int a[999999],t[999999];
int main(){
    n=read();
    for(int i=1;i<=n;i++){
        if(i&1)a[i]=read();
        else {
            (a[i]=read())^=1;
        }
        if(a[i]!=a[i-1])flag=1;
    }
    if(!flag)ans=n;
    for(int i=1;i<=n;i++){
        if(a[i]==a[i-1])t[tot]++;
        else t[++tot]=1;
    }
    for(int i=1;i<=tot;i++){
        ans=max(ans,t[i-1]+t[i]+t[i+1]);
    }
    printf("%d\n",ans);
    return 0;
}

T2:粘贴 (copy)

? Description

? 从前有一个 ${1, 2, \dots , n}$ 的排列,你希望用剪切/粘贴操作,将这个排列变成 $1,2, \dots , n$。

? 一次剪切/粘贴操作指的是把序列中某段连续的子区间整体向前或者向后平移一段距离。由于你认为同时按 Ctrl+X 很累手指,所以想要知道最少的操作次数。

? Input

? 输入文件包含多组数据。

? 每组数据有两行,其中第一行包含一个正整数 n,第二行给出一组 ${1, 2, ..., n}$ 的排列。

? 以一个 0 来结束输入。

? Output

? 对于每组数据,输出一行一个整数,表示最小的操作次数。

第二道题的话,还是要先%%%ZAGER大佬,搜索王实至名归(雾),全场就他一个人A掉了这道题,(好像也就只有他一个人得分?忘记了。。),而且他还是用的非常牛皮的IDA*,他说在当时讲课的时候,他有讲过这道题,而且真的在当时的ppt上找到了这道题,可能是因为当时不认真吧,唉,后悔。。。

这道题的题意是这样的:给你一个乱序的序列,由1到n的数字不重复组成。现在要求最少的步数通过平移某个小区间将这个序列转成严格上升的,也就是1,2,3……n这么排列。

数据范围是很友好的,eolv良心啊,本来想着可以开心地乱搞了,可就是因为这道题把自己整的很颓废,整场考试差点垮掉。

先说一下IDA吧,ZAGER大佬在给我们讲题的时候,提到了自己对IDA 的理解。

? ZAGER:IDA*=迭代加深+估价函数h();

个人感觉十分精辟,毕竟搜索菜鸡什么都不懂。

1,迭代加深:就是每一次给出一个深度,作为搜索步数的上界,深度从小到大给出,所以如果答案就是操作次数的话,迭代加深就可以保证第一次搜到的答案就是最小的,因为相当于从小到大枚举答案。

? 为什么要这么做?

? 因为针对于一部分搜索题,如果你开始搜到了错误的状态,是非常致命的,也就是说,如果在跑大法师的过程中搜到了一个错误状态并且还很深,那么基本上就GG了。那么对于这种情况,我们用类似广搜的思想来思考。就是在搜索的过程中,相当于我们是在从一棵搜索树的根节点不断向外扩展新的状态,一层一层,越来越多。那么为了避免我们钻进一棵子树再也上不来的情况,可以钦定一个深度,一旦当前的搜索深度到了这个深度并且还没有搜到,那么我们就选择放弃这棵子树,去到另外的子树也就是去搜索别的状态。这样即使刚开始几层的状态我们会重复搜到,也是可以有效解决搜不到底的问题的。

2,估价函数:个人感觉这东西太牛皮了,但是ZAGER大佬教导我的时候说这东西其实就是我们平时做题的一种思想,只不过把它叫做估价函数我就懵逼了。

? 想想还真是。

? 估价函数起到一个剪枝的作用。

? 联系迭代加深,我们可以知道搜索的总步数是给定的。

? 那么可以试想,当我们搜到一个状态的时候,已经用过的步数是已知的,一共可以用的步数也是已知的(就是迭代加深的最大深度),更重要的一点是,我们知道目标状态是什么,知道当前状态是什么,那么就可以估算出从当前状态到目标状态的代价也就是要用的步数,如果当前步数加上估算出的预期步数大于总步数,那么说明这种情况肯定不优或者说搜不到答案,直接返回就行,相当于一个剪枝。

? 为什么是估算?

? 很重要的一点就是,估价函数是我们就题论题分析出的,是指在最优决策下,从当前状态到达目标状态的期望步数,所以并不准确,但必须保证在最优状态下。因为如果在最优状态下都不能从当前状态到目标状态,我们就可以没有顾虑毫不犹豫地进行剪枝了。

? 对这道题分析,因为数据奇小,所以不用担心复杂度(有了IDA*就不用担心唯一会使我们超时的情况也就是搜不到底了)。又经过分析,我们可以得出只要在(n+1)/2步以内,又因为n<=9,所以答案最多是5.都会是任意一个错序序列变成目标序列,elov大佬说可以用分治证明,不过他并没有证,,,只是举了一个最坏情况下的例子

? 给出初始序列为:5 4 3 2 1

? 可以这么变换:
3 2 5 4 1 *1

? 3 4 1 2 5 *2

? 1 2 3 4 5 *3 也就是说在n=5的最坏情况下是可以3步解决问题的。

那么我们就可以优化,将迭代加深可以到的最大深度设为5,这里又有一个神奇的地方,就是我们可以将最大深度设为4,这时候如果还没有找到答案,那么不用往下搜也可以知道答案是5了,(分析问题真的很重要。。。,瑟瑟发抖)。

之后每一次变换,就可以先枚举一个小区间(左右端点),之后再枚举这个区间插到哪里就可以了。(真的是有了IDA*随便怎么乱搞都不怕。。。)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){
        if(ch==‘-‘)f=-1;
        ch=getchar();
    }
    while(ch>=‘0‘&&ch<=‘9‘){
        sum=(sum<<1)+(sum<<3)+ch-‘0‘;
        ch=getchar();
    }
    return sum*f;
}
const int wx=199;
int n,m;
int a[wx],t[wx];
bool goal(){
    for(int i=1;i<n;i++){
        if(a[i]!=a[i+1]-1)return false;
    }
    return true;
}
int h(){
    int tot=0;
    for(int i=1;i<n;i++){
        if(a[i+1]!=a[i]+1)tot++;
    }
    if(a[n]!=n)tot++;
    return tot;
}
bool dfs(int d,int maxd){
    if(h()+d*3>maxd*3)return false;//估价函数 大剪枝  很重要的一点:改变一个数最多会消掉3的不和谐值。
    if(goal())return true;//判断是否到达目标状态
    int b[wx],olda[wx];
    memcpy(olda,a,sizeof a);
    for(int i=1;i<=n;i++){
        for(int j=i;j<=n;j++){//i,j为枚举的区间左右端点
            int cnt=0;
            for(int k=1;k<=n;k++){
                if(k<i||k>j)b[++cnt]=a[k];//将不在i,j之间的数用中转数组b存一下
            }
            for(int k=1;k<=cnt;k++){//枚举i,j插在哪一个位置,暴力改变a数组即可
                int cnt2=0;
                for(int l=1;l<=k;l++)a[++cnt2]=b[l];//k是枚举i,j这个区间插入到第k个数后面。
                for(int l=i;l<=j;l++)a[++cnt2]=olda[l];
                for(int l=k+1;l<=cnt;l++)a[++cnt2]=b[l];
                if(dfs(d+1,maxd))return true;
                memcpy(a,olda,sizeof olda);//恢复a数组
            }
        }
    }
    return false;
}
int slove(){
    if(goal())return 0;
    int max_ans=5;
    for(int i=1;i<max_ans;i++){
        if(dfs(0,i))return i;//迭代步数
    }
    return max_ans;//分析问题真的很重要。。。
}
int main(){
    freopen("copy.in","r",stdin);
    freopen("copy.out","w",stdout);
    while(1){
        n=read();
        if(!n)break;
        for(int i=1;i<=n;i++){
            a[i]=read();
        }
        int ans=slove();
        printf("%d\n",ans);
    }
    return 0;
}

对于第二题 [UVa 11212 编辑书稿 ,查阅借鉴了一位大佬的思路,表示感谢并挂上链接。

? https://blog.csdn.net/jc514984625/article/details/51785439

10-2国庆节第四场模拟题题解

标签:数位   位置   左右   广搜   实现   思考   一个   查询   考试   

原文地址:https://www.cnblogs.com/wangxiaodai/p/9739061.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!