2011-0002

从 Trac 迁移的文章

这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。

原文章内容如下:

这道题目现在的数据范围已经改成n<=25了,大致算法是这样:
首先,因为我们希望切最多的水果,因此按照贪心的思路,我们如果在某个时刻,要切一些水果,那么我们必然是在所有目前可以切的水果中按照结束时间排序,结束时间早的先切。
基于这个思路,我们可以用f[i][mask]表示在时间i,剩下的水果状态是mask的情况下,我们的最大得分,然后我们枚举切多少水果,然后从mask中依次取当前可以切的结束时间最早的水果。
不过因为mask是指数级的,所以会暴内存。但是因为题目的特殊性(一次至少切3个水果,再加上我们的贪心策略),使得真正被使用到的状态实际上并不多,我们可以用Treap来存储状态。
然后就是时间问题,时间状态数据很大,但是真正有用的时间点最多2n个——即n个水果出现和消失的时间。(任何在中间时刻切的方案都可以移动到这2n个时间点中最近的,这样保证合法,且不会变差)
因此,我们的算法步骤大致如下:
1、将时间点离散化
2、把水果按照结束时间排序
3、初始只有一个状态,然后时间从1开始枚举,每次新得到的状态在下一次循环中使用
4、每一个时间点的每一个状态,临时状态mask1等于当前状态;然后从1~n枚举水果,每找到一个目前已经出现但是还没有切的水果,更新临时状态mask1,同时当前临时水果数加1;每找到一个水果,用临时水果数更新当前临时状态mask(如果是新出现的状态,必须要到下一时间点才能访问)
标程:

#include <cstdio>
#include <cstdlib>
#include <ctime>

int n,ans,tot,max,temp,mask,now,sum,dzhi,root;
int s[26],e[26];
int f[335544],b[335544];
int l[335544],r[335544],aux[335544];
int a[27];

void turn_r(int &i)\\左旋
{
    int j=l[i];
    l[i]=r[j];
    r[j]=i;
    i=j;    
}

void turn_l(int &i)\\右旋
{
    int j=r[i];
    r[i]=l[j];
    l[j]=i;
    i=j;    
}


int chazhao(int &i,int xx)\\查找的同时插入
{
    int o;
    if(i==0)
    {
        tot++;
        i=tot;  
        aux[i]=rand() % 335544;
        b[i]=xx;
        l[i]=0;r[i]=0;
        f[i]=0;
        return i;
    } else
    if(xx==b[i]) return i; else
    if(xx<b[i])
    {
        o=chazhao(l[i],xx);
        if(aux[l[i]]<aux[i]) turn_r(i);
        return o;   
    } else
    {
        o=chazhao(r[i],xx);
        if(aux[l[i]]<aux[i]) turn_l(i);
        return o;   
    }
}

void lisanhua()\\时间点离散化
{
    int f[51];
    for(int i=1;i<=n;i++) f[i]=s[i];
    for(int i=1;i<=n;i++) f[i+n]=e[i];
    int temp;
    for(int i=1;i<=n+n;i++)
    for(int j=i+1;j<=n+n;j++)
    if(f[i]>f[j])
    {
        temp=f[i];f[i]=f[j];f[j]=temp;
    }
    temp=0;
    for(int i=1;i<=n+n;i++)
    if(f[i]>f[i-1])
    {
        temp++;
        for(int j=1;j<=n;j++)
        {
            if(s[j]==f[i]) s[j]=temp;
            if(e[j]==f[i]) e[j]=temp;
        }
    }
    max=temp;
}

int main()
{
    a[1]=1;
    srand(time(0));
    for(int i=2;i<=26;i++) a[i]=a[i-1]*2;
    int temp;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
        scanf("%d %d",&s[i],&e[i]);
        for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
        if(e[i]>e[j])
        {
            temp=e[i];e[i]=e[j];e[j]=temp;
            temp=s[i];s[i]=s[j];s[j]=temp;
        }                               \\先对水果排序
        lisanhua();
        tot=0;
        temp=1;
        b[1]=a[n+1]-1;
        root=0;
        temp=chazhao(root,b[1]);
        for(int i=1;i<=max;i++)
        {
            for(int j=1;j<=temp;j++)
            {   
                mask=b[j];
                now=1;
                sum=0;
                while((now<=n)&&(((mask & a[now])==0)||(s[now]>i))) now++;\\取下一个可行的水果
                mask-=a[now];
                while((now<=n)&&(((mask & a[now])==0)||(s[now]>i))) now++;\\取下一个可行的水果
                mask-=a[now];
                while((now<=n)&&(((mask & a[now])==0)||(s[now]>i))) now++;\\取下一个可行的水果
                mask-=a[now];         \\先取三个水果
                if(now<=n)
                {
                    sum=3;
                    while(now<=n)
                    {   
                        dzhi=chazhao(root,mask);   \\查找状态
                        if(f[dzhi]<sum+f[j]) f[dzhi]=sum+f[j];    \\更新状态
                        while((now<=n)&&(((mask & a[now])==0)||(s[now]>i))) now++;\\取下一个可行的水果
                        mask-=a[now];   
                        sum++;
                    }
                }
            }
            temp=tot;
        }
        ans=0;
        for(int i=1;i<=tot;i++)
        if(f[i]>ans) ans=f[i];
        printf("%d\n",ans);
    }
    return 0;
}




by hzwlzrj

这道题目现在的数据范围已经改成n<=25了,大致算法是这样:

首先,因为我们希望切最多的水果,因此按照贪心的思路,我们如果在某个时刻,要切一些水果,那么我们必然是在所有目前可以切的水果中按照结束时间排序,结束时间早的先切。

基于这个思路,我们可以用f[i][mask]表示在时间i,剩下的水果状态是mask的情况下,我们的最大得分,然后我们枚举切多少水果,然后从mask中依次取当前可以切的结束时间最早的水果。

不过因为mask是指数级的,所以会暴内存。但是因为题目的特殊性(一次至少切3个水果,再加上我们的贪心策略),使得真正被使用到的状态实际上并不多,我们可以用Treap来存储状态。

然后就是时间问题,时间状态数据很大,但是真正有用的时间点最多2n个——即n个水果出现和消失的时间。(任何在中间时刻切的方案都可以移动到这2n个时间点中最近的,这样保证合法,且不会变差)

因此,我们的算法步骤大致如下:

1、将时间点离散化

2、把水果按照结束时间排序

3、初始只有一个状态,然后时间从1开始枚举,每次新得到的状态在下一次循环中使用

4、每一个时间点的每一个状态,临时状态mask1等于当前状态;然后从1~n枚举水果,每找到一个目前已经出现但是还没有切的水果,更新临时状态mask1,同时当前临时水果数加1;每找到一个水果,用临时水果数更新当前临时状态mask(如果是新出现的状态,必须要到下一时间点才能访问)

标程:

#include

#include

#include

int n,ans,tot,max,temp,mask,now,sum,dzhi,root;

int s[26],e[26];

int f[335544],b[335544];

int l[335544],r[335544],aux[335544];

int a[27];

void turn_r(int &i)\\左旋

{

int j=l[i];

l[i]=r[j];

r[j]=i;

i=j;

}

void turn_l(int &i)\\右旋

{

int j=r[i];

r[i]=l[j];

l[j]=i;

i=j;

}

int chazhao(int &i,int xx)\\查找的同时插入

{

int o;

if(i==0)

{

tot++;

i=tot;

aux[i]=rand() % 335544;

b[i]=xx;

l[i]=0;r[i]=0;

f[i]=0;

return i;

} else

if(xx==b[i]) return i; else

if(xx

{

o=chazhao(l[i],xx);

if(aux[l[i]]

return o;

} else

{

o=chazhao(r[i],xx);

if(aux[l[i]]

return o;

}

}

void lisanhua()\\时间点离散化

{

int f[51];

for(int i=1;i<=n;i++) f[i]=s[i];

for(int i=1;i<=n;i++) f[i+n]=e[i];

int temp;

for(int i=1;i<=n+n;i++)

for(int j=i+1;j<=n+n;j++)

if(f[i]>f[j])

{

temp=f[i];f[i]=f[j];f[j]=temp;

}

temp=0;

for(int i=1;i<=n+n;i++)

if(f[i]>f[i-1])

{

temp++;

for(int j=1;j<=n;j++)

{

if(s[j]==f[i]) s[j]=temp;

if(e[j]==f[i]) e[j]=temp;

}

}

max=temp;

}

int main()

{

a[1]=1;

srand(time(0));

for(int i=2;i<=26;i++) a[i]=a[i-1]*2;

int temp;

while(scanf("%d",&n)!=EOF)

{

for(int i=1;i<=n;i++)

scanf("%d %d",&s[i],&e[i]);

for(int i=1;i<=n;i++)

for(int j=i+1;j<=n;j++)

if(e[i]>e[j])

{

temp=e[i];e[i]=e[j];e[j]=temp;

temp=s[i];s[i]=s[j];s[j]=temp;

} \\先对水果排序

lisanhua();

tot=0;

temp=1;

b[1]=a[n+1]-1;

root=0;

temp=chazhao(root,b[1]);

for(int i=1;i<=max;i++)

{

for(int j=1;j<=temp;j++)

{

mask=b[j];

now=1;

sum=0;

while((now<=n)&&(((mask & a[now])==0)||(s[now]>i))) now++;\\取下一个可行的水果

mask-=a[now];

while((now<=n)&&(((mask & a[now])==0)||(s[now]>i))) now++;\\取下一个可行的水果

mask-=a[now];

while((now<=n)&&(((mask & a[now])==0)||(s[now]>i))) now++;\\取下一个可行的水果

mask-=a[now]; \\先取三个水果

if(now<=n)

{

sum=3;

while(now<=n)

{

dzhi=chazhao(root,mask); \\查找状态

if(f[dzhi]

while((now<=n)&&(((mask & a[now])==0)||(s[now]>i))) now++;\\取下一个可行的水果

mask-=a[now];

sum++;

}

}

}

temp=tot;

}

ans=0;

for(int i=1;i<=tot;i++)

if(f[i]>ans) ans=f[i];

printf("%d\n",ans);

}

return 0;

}

by hzwlzrj