The All-purpose Zero
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 935 Accepted Submission(s): 451
For each case,the first line contains an interger n,which is the length of the array s.
The next line contains n intergers separated by a single space, denote each number in S.
思路:貪心+dp:看着題解,到現在還不是理解的很徹底,先引用一下題解,抽時間再理解理解。
題解:0可以轉化成任意整數,包括負數,顯然求LIS時儘量把0都放進去必定是正確的。因此我們可以把0拿出來,對剩下的做O(nlogn)的LIS,統計結果的時候再算上0的數量。爲了保證嚴格遞增,我們可以將每個權值S[i]減去i前面0的個數,再做LIS,就能保證結果是嚴格遞增的。
線段樹+dp:還是數據結構好,雖然代碼多,但好理解,而且複雜度和貪心+dp的一樣。
dp[i]表示長度爲i的上升序列結尾最小是多少。當A[i] == 0 時,其實對於dp數組就是整體加1後右移,也就是for each i ,dp[i+1]=dp[i]+1 ,這裏可以拿線段樹來實現,區間更新(加1),左邊界左移。不過有一點值得注意:dp數組要初始化爲 -INF(或小於-1000000的任一個值)。不然1 0 2 0 1 0 4 0 5 0運行出來可能是6,答案是7,有的程序這個數據不對也能過了,總之數據比較水。具體可以看代碼。
聽說拿樹狀數組或二分+單調棧也可以實現。不過這題數據比較水,當時隊友拿n^2的直接78ms就過了,他在A[i]==0的時候拿循環更新的。
詳細見代碼:
貪心+dp:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 1e5+100;
#define INF 0x3f3f3f3f
int dp[maxn];
int A[maxn];
int main()
{
int t,case1=0;
scanf("%d",&t);
while(t--)
{
int n;
int i,j;
printf("Case #%d: ",++case1);
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&A[i]);
dp[i]=INF;
}
dp[0]=INF;
int ans=0;
for(i=1;i<=n;i++)
{
if(A[i]==0)
ans++;
else
*lower_bound(dp,dp+n,A[i]-ans)=A[i]-ans;
}
printf("%d\n",(int)(lower_bound(dp,dp+n+1,INF)-dp+ans));
}
return 0;
}
線段樹+dp:
//時間202ms
//用線段樹B求最長上升子序列長度
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
const int maxn=1e5+100;
int A[maxn];
int B[maxn<<2];
int cnt[maxn<<2];
int n;
inline void push_down(int rt) //區間更新的延遲更新
{
if(cnt[rt])
{
cnt[rt<<1]+=cnt[rt];
cnt[rt<<1|1]+=cnt[rt];
B[rt<<1]+=cnt[rt];
B[rt<<1|1]+=cnt[rt];
cnt[rt]=0;
}
}
void update(int L,int R,int l,int r,int rt)//區間更新,整體+1
{
if(L<=l&&R>=r)
{
B[rt]++;
cnt[rt]++;
return ;
}
int mid=(l+r)>>1;
push_down(rt);
if(L<=mid) update(L,R,lson);
if(R>mid) update(L,R,rson);
B[rt]=max(B[rt<<1],B[rt<<1|1]);
}
void change(int pos,int val,int l,int r,int rt) //改變pos位置的值
{
if(l==r)
{
B[rt]=val;
return ;
}
int mid=(l+r)>>1;
push_down(rt);
if(pos<=mid) change(pos,val,lson);
else change(pos,val,rson);
B[rt]=max(B[rt<<1],B[rt<<1|1]);
}
void work(int val,int l,int r,int rt) //LIS把長度爲i的結尾的值改爲更小的值val,直接利用線段樹的結構來更新
{
if(l==r)
{
B[rt]=val;
return;
}
int mid=(l+r)>>1;
push_down(rt);
if(B[rt<<1]>=val) work(val,lson);
else work(val,rson);
B[rt]=max(B[rt<<1],B[rt<<1|1]);
}
void solve()
{
int ll=n;
int rr=n;
int i;
for(i=1;i<=n;i++)//優化一下dp數組剛開始時的範圍,因爲後面要左邊界左移,優化一下,避免開更大的內存
{
if(A[i]!=0)
rr--;
}
ll=rr+1;
for(i=1;i<=n;i++)
{
if(A[i]==0)
{
if(rr>=ll)
{
update(ll,rr,1,n,1); //整體加1
}
ll--;//左邊界左移
}
else
{//這裏就是LIS的內容了,B是線段樹的數組,也就是dp數組吧
if(B[1]<A[i])
{
rr++;
change(rr,A[i],1,n,1);
}
else
{
work(A[i],1,n,1);
}
}
}
printf("%d\n",rr-ll+1);
}
int main()
{
int t,case1=0;
scanf("%d",&t);
while(t--)
{
printf("Case #%d: ",++case1);
scanf("%d",&n);
int i,j;
for(i=1;i<(maxn<<2);i++) //線段樹初始爲-INF
B[i]=-INF;
memset(cnt,0,sizeof cnt);
for(i=1;i<=n;i++)
{
scanf("%d",&A[i]);
}
solve();
}
return 0;
}