[USACO18OPEN]Talent Show 解题报告

题目:传送门(洛谷)

这道题可谓刷新了我对0/1分数划分的认识。

之前做过一道最小生成树+0/1分数划分(在我的博客里也有),但这次写的时候没联想到背包问题,考场(noip模拟)上直接上了遗传然后老师多次测评取最低值就只12分了qwq。

如果对0/1分数划分不熟悉的读者可以戳这里阅读相关文章(我觉得我写得还不错(雾))

首先不难发现我们在标操之后,要选出一些牛,这些牛的w(体重)和要大于等于W(体重下限),且他们的评估函数(t-mid*w)之和是否大于等于0决定了mid是大了还是小了(差点写成中考经典病句)。

而怎么选这些牛,就是一个相对经典的背包问题,dp[v]表示用了v的体重,能收集到的最大评估函数的和。

套进背包问题的模板里就行了。

那么代码如下:

#include<iostream>
#include<cstdio>
#define maxn 300
using namespace std;
inline void read(int &x)
{
    x=0;int f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
inline void read(double &x)
{
    x=0;int f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
int N,W;
double w[maxn],t[maxn];
double c[maxn],dp[1005];
bool check(double mid)
{
    for(int i=1;i<=N;i++)
        c[i]=t[i]-mid*w[i];
    for(int i=0;i<=W;i++)
        dp[i]=-0x3f3f3f3f;
    dp[0]=0;
/*    for(int v=0;v<=W;v++)
    {
        for(int i=1;i<=N;i++)
        {
            int tmp=v+w[i];
            if(tmp>W)
                tmp=W;
            dp[tmp]=max(dp[tmp],dp[v]+c[i]);
        }
    }*/
    for(int i=1;i<=N;i++)
        for(int v=W;v>=0;v--)
        {
            int tmp=v+w[i];
            if(tmp>W)
                tmp=W;
            dp[tmp]=max(dp[tmp],dp[v]+c[i]);
        }
//    cout<<mid<<"   "<<dp[W]<<endl;
    if(dp[W]>=0.0)
        return 1;
    else    
        return 0;
}
int main()
{
    read(N);read(W);
    double tw=0,tt=0;
    for(int i=1;i<=N;i++)
    {
        read(w[i]);
        read(t[i]);
        tw+=w[i];
        tt+=t[i];
    }
    double l=0,r=100005,mid,ans;
    while(r-l>0.00001)
    {
        mid=(r+l)/2;
        //cout<<l<<"   "<<r<<"   ";
        if(check(mid))
            l=mid,ans=mid;
        else
            r=mid;
    }
    ans*=1000;
    printf("%d",int(ans));
}
View Code

--------------------Warning:下面的内容不是正解!!!------------------------

然后我在考场上写的伪遗传在遗传8000次左右可以保证正确,但是时间会高达10s左右。

我提交的版本为了保证时间,只遗传了400代(事实证明一秒内我可以遗传800代甚至更多)。

为了保证时间,我甚至没有写交换。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<algorithm>
#define maxn 300
#define yep cout<<"yep"<<endl;
using namespace std;
inline void read(double &x)
{
    x=0;int f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
double N,W;
double w[maxn],t[maxn];
struct node{
    int u[maxn];
    double f;
    void cal()
    {
        double wt=0,tt=0;
        for(int i=1;i<=N;i++)
            if(u[i])
                wt+=w[i],tt+=t[i];
        if(wt<W)
            f=-1;
        else
            f=tt/wt;
    }
    friend bool operator < (node a,node b)
    {
        return a.f>b.f;
    }
}group[1005];
inline void getnew(int x)
{
    for(int i=1;i<=N;i++)
        group[x].u[i]=rand()%2;
    group[x].cal();
}
node fuck(node fa,node ma)
{
    int c;
    node child;
    for(int i=1;i<=N;i++)
    {
        c=rand()%2;
        if(c)
            child.u[i]=fa.u[i];
        else
            child.u[i]=ma.u[i];
    }
    child.cal();
    return child;
}
int main()
{
    freopen("talent.in","r",stdin);
    freopen("talent.out","w",stdout);
    srand(time(NULL));
    read(N);read(W);
    double tw=0,tt=0;
    for(int i=1;i<=N;i++){read(w[i]);read(t[i]);}
    int T=400;
    for(int i=1;i<=T;i++)    
    {
        while(group[i].f<=0)
            getnew(i);
    }
    for(int i=1;i<=T;i++)
    {
        sort(group+1,group+T+1);
        for(int i=T/2+1;i<=T;i++)
            group[i]=fuck(group[i-T/2],group[i-T/2+1]);
    }
    sort(group+1,group+T+1);
    double ans;
    ans=group[1].f;
    ans*=1000;
    printf("%d",int(ans));
}
View Code

 

posted @ 2018-10-16 15:59  溡沭  阅读(205)  评论(1编辑  收藏  举报