1. 程式人生 > >10.21離線賽

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;
}