10.21离线赛

一、完美01正方形

数据:对于100%,n、m∈[1,300]

注意一点,题目问的是正方形,不是矩形。那么枚举一个左上角和正方形边长,这样才n^3,然后用二维前缀和判断,就可以过了。

二、重温LIS

数据:对于70%,n∈[1,1000]
对于100%,n∈[1,10^5],LIS的长度不超过1000

快速求LIS的方法大家也都知道,这里就不介绍了。

问题多了一个就是判断一个数是否在LIS上。肯定在很简单,如果删了LIS变了,那肯定是;问题是可能在和不在两个。我的判断是判断肯定在和肯定不在,剩下的就是可能在。肯定不在只要从dp值最大的往前走,然后把走到过的点都标记一下,没标记的就是不可能的点。

#include<bits/stdc++.h>
#define M 100005
using namespace std;
int A[M],dp[M],n,D[M],cnt[M],Q[M];
struct BITS{//优化LIS
    int a[M];
    int Query(int x){
        int res=0;
        while(x){
            if(a[x]>res)res=a[x];
            x-=x&-x;
        }
        return res;
    }
    void Change(int x,int b){
        while(x<=100000){
            if(a[x]<b)a[x]=b;
            x+=x&-x;
        }
    }
}Bit;
vector<int>G[M];
void f(int x,int dep){
    cnt[dep]++;//同深度的个数(也可以理解为在这个dp值状态下的个数)
    Q[x]=1;D[x]=dep;//标记到过的点,记录点的深度
    for(int i=0;i<(int)G[dp[x]-1].size();i++){
        int y=G[dp[x]-1][i];
        if(Q[y]==1||A[y]>=A[x]||x<y)continue;
        f(y,dep+1);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&A[i]);
    int ans=1;
    for(int i=1;i<=n;i++){
        dp[i]=1;
        if(A[i]==0)continue;
        int x=Bit.Query(A[i]-1);
        dp[i]=x+1;
        Bit.Change(A[i],dp[i]);
        if(ans<dp[i])ans=dp[i];
        G[dp[i]].push_back(i);//存起来
    }
    for(int i=1;i<=n;i++)if(dp[i]==ans)f(i,1);//从最后一个走
    for(int i=1;i<=n;i++){
        if(Q[i]==0)printf("1");//不能
        else if(cnt[D[i]]==1)printf("3");//能
        else printf("2");//可能
    }
    return 0;
}

三、画画

数据:对于20%,n∈[1,20],m∈[1,5]
对于20%,n∈[1,2000],m∈[1,100]
对于100%,n∈[1,20000],m∈[1,100]

考试时交了20分的深搜。

其实我想了很多。dp想了,但是debug了好久也没过大数据。二分想了,但是debug好久也没过大数据。结束后发现,dp循环其实去掉一层就行了,二分的判定用贪心是错的……正解是二分+dp。

先二分弄出一个最大的画布长度,这样少掉了一维。这样以后还剩下的就只有两层的n了,画布数量是要求的,这样就简单多了。定义dp[i]是到i这个位置时在此画布长度下最少要多少副画。那么只要一遍循环过去就行了,是n^2。然后注意到其中有许多地方的dp值和需要的画布数是一样的,循环重复太多。那么就跳掉那些。先预处理每个画布往前可以到的地方(这是因为我的dp循环是倒着的),这样就能过了。

卡了很久。一是错在预处理两行一起的时候如果两个的长度大于x时,就直接返回-1了,但实际上再前面的点其实可以继续往前的。二是预处理的是会到这张画布的起点的前一个位置。但是原来我是到画布的起点,那时还要减个一。

#include<bits/stdc++.h>
#define M 500005
using namespace std;
int n,m,A[2][M],dp[M],cnt[2][M],las[3][M];
void Init(int x){//这段写的很丑,就是用尺取的方法计算每个点往前走回到哪儿
    memset(las,0,sizeof las);
    int l0=n,r0=n;
    while(l0<=r0&&l0>0)if(cnt[0][r0]-cnt[0][l0-1]>x)las[0][r0]=l0,r0--;else l0--;
    for(int i=1;i<=r0;i++)las[0][i]=0;
    int l1=n,r1=n;
    while(l1<=r1&&l1>0)if(cnt[1][r1]-cnt[1][l1-1]>x)las[1][r1]=l1,r1--;else l1--;
    for(int i=1;i<=r1;i++)las[1][i]=0;
    l1=n,r1=n;
    while(l1<=r1&&l1>0){
        if(cnt[0][r1]-cnt[0][l1-1]+cnt[1][r1]-cnt[1][l1-1]>x&&l1==r1){
            las[2][r1]=-1;
            l1--;r1--;
        }
        else if(cnt[0][r1]-cnt[0][l1-1]+cnt[1][r1]-cnt[1][l1-1]>x)las[2][r1]=l1,r1--;else l1--;
    }
    for(int i=1;i<=r1;i++)las[2][i]=0;
}
bool check(int x){
    memset(dp,63,sizeof dp);
    dp[0]=0;
    for(int i=1;i<=n;i++){
        int C0=0,C1=0,X0=i,X1=i;
        while(X0!=0||X1!=0){//第一行第二行各走各的
            if(X0==0)X1=las[1][X1],C1++;else if(X1==0)X0=las[0][X0],C0++;
            else if(X0>X1)X0=las[0][X0],C0++;else X1=las[1][X1],C1++;
            dp[i]=min(dp[i],dp[max(X0,X1)]+C0+C1);
        }
        int C=0,X=i;
        while(X!=0){//两行一起走
            X=las[2][X];C++;
            if(X==-1)break;
            dp[i]=min(dp[i],dp[X]+C);
        }
    }
    return dp[n]<=m;
}
int main(){
    scanf("%d%d",&n,&m);
    int l=0,r=0,ans=0;
    for(int i=1;i<=n;i++)scanf("%d",&A[0][i]),r+=A[0][i],l=max(l,A[0][i]),cnt[0][i]+=cnt[0][i-1]+A[0][i];
    for(int i=1;i<=n;i++)scanf("%d",&A[1][i]),r+=A[1][i],l=max(l,A[1][i]),cnt[1][i]+=cnt[1][i-1]+A[1][i];
    while(l<=r){
        int mid=(l+r)/2;
        Init(mid);
        if(check(mid))r=mid-1,ans=mid;
        else l=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}

还有一种做法,那种更快。定义dp[i]是用了i张画布最远到哪里。然后依旧是预处理,这次预处理每个点往后能走到哪里。

#include<bits/stdc++.h>
#define M 20005
using namespace std;
int n,m,A[2][M],dp[105],cnt[3][M],nxt[3][M];
bool check(int x){
    memset(dp,0,sizeof dp);
    int R1=1,R2=1,R3=1;
    for(int i=1;i<=n;i++){//往后走的预处理
        while(R1<=n&&cnt[0][R1]-cnt[0][i-1]<=x)R1++;nxt[0][i]=R1-1;
        while(R2<=n&&cnt[1][R2]-cnt[1][i-1]<=x)R2++;nxt[1][i]=R2-1;
        while(R3<=n&&cnt[2][R3]-cnt[2][i-1]<=x)R3++;nxt[2][i]=R3-1;
    }
    nxt[0][n+1]=nxt[1][n+1]=nxt[2][n+1]=n;
    for(int i=0;i<m;i++){
        dp[i+1]=max(dp[i+1],nxt[2][dp[i]+1]);//两行一起
        int j=i+2,l1=dp[i]+1,l2=dp[i]+1;
        while(j<=m){
            int res=min(nxt[0][l1],nxt[1][l2]);//选一个走出去比较近的
            dp[j]=max(dp[j],res);//更新一下
            if(nxt[0][l1]==res)l1=res+1,j++;
            if(nxt[1][l2]==res)l2=res+1,j++;
            //如果等于,走的就是那一行,然后j(此时的画布数)多一个,那行画到res+1位置
        }
    }
    return dp[m]==n;
}
int main(){
    scanf("%d%d",&n,&m);
    int l=0,r=0,ans=0;
    for(int i=1;i<=n;i++)scanf("%d",&A[0][i]),r+=A[0][i],l=max(l,A[0][i]),cnt[0][i]+=cnt[0][i-1]+A[0][i],cnt[2][i]+=cnt[0][i];
    for(int i=1;i<=n;i++)scanf("%d",&A[1][i]),r+=A[1][i],l=max(l,A[1][i]),cnt[1][i]+=cnt[1][i-1]+A[1][i],cnt[2][i]+=cnt[1][i];
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid))r=mid-1,ans=mid;
        else l=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值