帮助Bsny(乱搞做法)

帮助Bsny

题目描述

Bsny的书架乱成一团了,帮他一下吧!

他的书架上一共有n本书,我们定义混乱值是连续相同高度书本的段数。例如,如果书的高度是30,30,31,31,32,那么混乱值为3;30,32,32,31的混乱值也为3。但是31,32,31,32,31的混乱值为5,这实在是太乱了。

Bsny想尽可能减少混乱值,但他有点累了,所以他决定最多取出k本书,再随意将它们放回到书架上。你能帮助他吗?

输入

第一行两个整数n,k,分别表示书的数目和可以取出的书本数目。

接下来一行n个整数表示每本书的高度。

输出

仅一行一个整数,表示能够得到的最小混乱值。

样例输入

5 1
25 26 25 26 25

样例输出

3

提示

20%的数据:1≤n≤20,k=1。

40%的数据:书的高度不是25就是32,高度种类最多2种。

100%的数据:1≤k≤n≤100,注意所有书本高度在[25,32]。

来源

NOIP2014八校联考Test2 Day2

solution:

这道题是两年前出的,已经有很多题解了。正解是DP,比较复杂的状态压缩动态规划,这里就不多讲了。我将要讲另一种方法(纯属瞎搞),虽然很难AC,但平均可以得90分,在比赛里是很值的(这种方法不怎么用想,很容易实现,得分效率高,在不会做的时候是个不错的方法)。看到n比较小,但2^n枚举每本书是否取出肯定不行,不过还不算太离谱。想想曾经zhw学长教的一种方法——模拟退火法,在这题里似乎可行。在状态确定的情况下,可以轻松地在O(n)的时间里算出混乱度,然后直接套模拟退火法的模板就好了。可是,这种方法不常用,以至于我把退火的概率公式忘记了……然后随便编了一个,大概的趋势有那么一点像,但不靠谱。
这是我练习赛是的代码,公式乱造,只有55分(还可以了)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long ll;
ll read(){
    ll ans=0;
    char ch=getchar(),last=' ';
    while(ch>'9'||ch<'0'){
        last=ch;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0'){
        ans=ans*10+ch-'0';
        ch=getchar();
    }
    if(last=='-')
        ans=-ans;
    return ans;
}
int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot;
bool c[105];
double T;
const double ze=1e-8;
int random(int x){
    unsigned int ans=x;
    ans*=1234567891;
    return ans;
}
int main(){
    //freopen("bb.in","r",stdin);
    n=read();
    m=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),b[a[i]]++;
    T=10000;
    ans=n;
    for(int i=1;i<=m;i++)
        c[i]=true,b[a[i]]--,d[a[i]]++;
    if(n==m){
        ans=0;
        for(int i=25;i<=32;i++)
            if(d[i])
                ans++;
        printf("%d\n",ans);
        return 0;
    }
    while(T>ze){
        //tot++;
        x=rand()%n+1;
        while(c[x])
            x=rand()%n+1;
        y=rand()%n+1;
        while(!c[y])
            y=rand()%n+1;
        c[x]=true;
        c[y]=false;
        b[a[x]]--;
        b[a[y]]++;
        d[a[x]]++;
        d[a[y]]--;
        sum=0;
        last=1;
        while(c[last])
            last++;
        if(last<=n){
            sum=1;
            pre=last;
            for(int i=last+1;i<=n;i++)
                if(!c[i]&&a[pre]!=a[i]){
                    sum++;
                    pre=i;
                }
        }
        for(int i=25;i<=32;i++)
            if(d[i]&&!b[i])
                sum++;
        if(ans>sum)
            ans=sum;
        else{
            if(rand()%25+1>log(T+20)){
                c[x]=false;
                c[y]=true;
                b[a[x]]++;
                b[a[y]]--;
                d[a[x]]--;
                d[a[y]]++;
            }
        }
        T*=0.99998;
    }
    printf("%d\n",ans);
    //printf("%d %d\n",ans,tot);
    return 0;
}

赛后百度了一下正宗的模拟退火法,把代码修改一下,是这样的(90分,差不多了)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long ll;
ll read(){
    ll ans=0;
    char ch=getchar(),last=' ';
    while(ch>'9'||ch<'0'){
        last=ch;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0'){
        ans=ans*10+ch-'0';
        ch=getchar();
    }
    if(last=='-')
        ans=-ans;
    return ans;
}
int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot;
bool c[105];
double T,de;
const double ze=1e-8;
int main(){
    //freopen("bb.in","r",stdin);
    n=read();
    m=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),b[a[i]]++;
    T=100000;
    ans=n;
    for(int i=1;i<=m;i++)
        c[i]=true,b[a[i]]--,d[a[i]]++;
    if(n==m){
        ans=0;
        for(int i=25;i<=32;i++)
            if(d[i])
                ans++;
        printf("%d\n",ans);
        return 0;
    }
    if(m==0){
        printf("%d\n",n);
        return 0;
    }
    while(T>ze){
        //tot++;
        x=rand()%n+1;
        while(c[x])
            x=rand()%n+1;
        y=rand()%n+1;
        while(!c[y])
            y=rand()%n+1;
        c[x]=true;
        c[y]=false;
        b[a[x]]--;
        b[a[y]]++;
        d[a[x]]++;
        d[a[y]]--;
        sum=0;
        last=1;
        while(c[last])
            last++;
        if(last<=n){
            sum=1;
            pre=last;
            for(int i=last+1;i<=n;i++)
                if(!c[i]&&a[pre]!=a[i]){
                    sum++;
                    pre=i;
                }
        }
        for(int i=25;i<=32;i++)
            if(d[i]&&!b[i])
                sum++;
        if(ans>sum)
            ans=sum;
        else{
            de=sum-ans;
            if((1.0/exp(de/T))*100<=rand()%100+1){
                c[x]=false;
                c[y]=true;
                b[a[x]]++;
                b[a[y]]--;
                d[a[x]]--;
                d[a[y]]++;
            }
        }
        T*=0.99998;
    }
    printf("%d\n",ans);
    //printf("%d %d\n",ans,tot);
    return 0;
}

可惜这样得不了ac,然后ctime不能用,srand()也没办法,怎么办?手动改随机种子
这个95分

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long ll;
ll read(){
    ll ans=0;
    char ch=getchar(),last=' ';
    while(ch>'9'||ch<'0'){
        last=ch;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0'){
        ans=ans*10+ch-'0';
        ch=getchar();
    }
    if(last=='-')
        ans=-ans;
    return ans;
}
int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot;
bool c[105];
double T,de;
const double ze=1e-8;
int main(){
    //freopen("bb.in","r",stdin);
    for(int i=1;i<=160;i++)
        n=rand();
    n=read();
    m=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),b[a[i]]++;
    T=1000000;
    ans=n;
    for(int i=1;i<=m;i++)
        c[i]=true,b[a[i]]--,d[a[i]]++;
    if(n==m){
        ans=0;
        for(int i=25;i<=32;i++)
            if(d[i])
                ans++;
        printf("%d\n",ans);
        return 0;
    }
    if(m==0){
        printf("%d\n",n);
        return 0;
    }
    while(T>ze){
        //tot++;
        x=rand()%n+1;
        while(c[x])
            x=rand()%n+1;
        y=rand()%n+1;
        while(!c[y])
            y=rand()%n+1;
        c[x]=true;
        c[y]=false;
        b[a[x]]--;
        b[a[y]]++;
        d[a[x]]++;
        d[a[y]]--;
        sum=0;
        last=1;
        while(c[last])
            last++;
        if(last<=n){
            sum=1;
            pre=last;
            for(int i=last+1;i<=n;i++)
                if(!c[i]&&a[pre]!=a[i]){
                    sum++;
                    pre=i;
                }
        }
        for(int i=25;i<=32;i++)
            if(d[i]&&!b[i])
                sum++;
        if(ans>sum)
            ans=sum;
        else{
            de=sum-ans;
            if((1.0/exp(de/T))*100<=rand()%100+1){
                c[x]=false;
                c[y]=true;
                b[a[x]]++;
                b[a[y]]--;
                d[a[x]]--;
                d[a[y]]++;
            }
        }
        T*=0.99998;
    }
    printf("%d\n",ans);
    //printf("%d %d\n",ans,tot);
    //printf("%.6lf\n",exp(2));
    return 0;
}

难道到极限了吗?当然没有,一定可以ac的,我调了半天一直WA,同学看了一下,只改了一个字符,就ac了(强)。
下面是ac的代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long ll;
ll read(){
    ll ans=0;
    char ch=getchar(),last=' ';
    while(ch>'9'||ch<'0'){
        last=ch;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0'){
        ans=ans*10+ch-'0';
        ch=getchar();
    }
    if(last=='-')
        ans=-ans;
    return ans;
}
int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot;
bool c[105];
double T,de;
const double ze=1e-8;
int main(){
    //freopen("bb.in","r",stdin);
    for(int i=1;i<=160;i++)
        n=rand();
    n=read();
    m=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),b[a[i]]++;
    T=2000000;
    ans=n;
    for(int i=1;i<=m;i++)
        c[i]=true,b[a[i]]--,d[a[i]]++;
    if(n==m){
        ans=0;
        for(int i=25;i<=32;i++)
            if(d[i])
                ans++;
        printf("%d\n",ans);
        return 0;
    }
    if(m==0){
        printf("%d\n",n);
        return 0;
    }
    while(T>ze){
        tot++;
        x=rand()%n+1;
        while(c[x])
            x=rand()%n+1;
        y=rand()%n+1;
        while(!c[y])
            y=rand()%n+1;
        c[x]=true;
        c[y]=false;
        b[a[x]]--;
        b[a[y]]++;
        d[a[x]]++;
        d[a[y]]--;
        sum=0;
        last=1;
        while(c[last])
            last++;
        if(last<=n){
            sum=1;
            pre=last;
            for(int i=last+1;i<=n;i++)
                if(!c[i]&&a[pre]!=a[i]){
                    sum++;
                    pre=i;
                }
        }
        for(int i=25;i<=32;i++)
            if(d[i]&&!b[i])
                sum++;
        if(ans>sum)
            ans=sum;
        else{
            de=sum-ans;
            if((1.0/exp(de/T))*100<=(double)(rand()%100+1)){
                c[x]=false;
                c[y]=true;
                b[a[x]]++;
                b[a[y]]--;
                d[a[x]]--;
                d[a[y]]++;
            }
        }
        T*=0.999978;
    }
    printf("%d\n",ans);
    //printf("%d %d\n",ans,tot);
    return 0;
}

调随机种子是很傻的做法(比赛时不可能完成),调参数才更有效。
同样AC,不要调随机种子,在退火概率中,还有一个系数是可以改的。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef long long ll;
ll read(){
    ll ans=0;
    char ch=getchar(),last=' ';
    while(ch>'9'||ch<'0'){
        last=ch;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0'){
        ans=ans*10+ch-'0';
        ch=getchar();
    }
    if(last=='-')
        ans=-ans;
    return ans;
}
int n,m,a[200],b[100],d[100],x,y,ans,sum,last,pre,tot;
bool c[105];
double T,de;
const double ze=1e-8;
int main(){
    n=read();
    m=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),b[a[i]]++;
    T=2000000;
    ans=n;
    for(int i=1;i<=m;i++)
        c[i]=true,b[a[i]]--,d[a[i]]++;
    if(n==m){
        ans=0;
        for(int i=25;i<=32;i++)
            if(d[i])
                ans++;
        printf("%d\n",ans);
        return 0;
    }
    if(m==0){
        printf("%d\n",n);
        return 0;
    }
    while(T>ze){
        tot++;
        x=rand()%n+1;
        while(c[x])
            x=rand()%n+1;
        y=rand()%n+1;
        while(!c[y])
            y=rand()%n+1;
        c[x]=true;
        c[y]=false;
        b[a[x]]--;
        b[a[y]]++;
        d[a[x]]++;
        d[a[y]]--;
        sum=0;
        last=1;
        while(c[last])
            last++;
        if(last<=n){
            sum=1;
            pre=last;
            for(int i=last+1;i<=n;i++)
                if(!c[i]&&a[pre]!=a[i]){
                    sum++;
                    pre=i;
                }
        }
        for(int i=25;i<=32;i++)
            if(d[i]&&!b[i])
                sum++;
        if(ans>sum)
            ans=sum;
        else{
            de=sum-ans;
            if((1.0/(exp(de/(T*1.2))))*100<=(double)(rand()%100+1)){
                c[x]=false;
                c[y]=true;
                b[a[x]]++;
                b[a[y]]--;
                d[a[x]]--;
                d[a[y]]++;
            }
        }
        T*=0.999978;
    }
    printf("%d\n",ans);
    //printf("%d %d\n",ans,tot);
    return 0;
}

于是,这种费正解也把这道题ac了。如果书的高度种类有很多的话,这种方法可以比正解更好用,难道不是吗?

本片文章纯属乱搞,神犇不要喷……

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38601996/article/details/75206250
个人分类: 模拟退火法
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

帮助Bsny(乱搞做法)

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭