2012-A3-0024

从 Trac 迁移的文章

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

原文章内容如下:

题意:一线排列的积木,每个积木有一定的高度,每个可以沿一个方向推倒,会引起连锁反应,问最少碰倒多少个可以推到全部。

按x坐标排序是必不可少的。

先预处理出,两个方向推到一个,可以推到到对应方向的最远位置,l[i],r[i],最坏O(n^2^)可以预处理出来,

于是很容易想出O(n^2^)的DP,f[i]表示从左至右推倒到i最少要碰倒几个. f[i] = min(f[l[i]-1]+1,f[i]),再以之f[i]+1更新f[j, j from i+1 to r[i]]. 

这样可以n^2^解决这个问题,同时预处理和DP的过程都可以用线段树优化到O(n log n),这样就可以在O(n log n)解决这个问题。

实际上,不算排序,可以用O(n)的方法,解决它,实现也比较简单。

L[i]和R[i]意义和上面一样,需要预处理,

注意到,每次推到如果引起连锁反应,必然只需要找到它只靠自己能推的最远的位置,剩下的要靠它推倒的积木的连锁反应来延续,

于是只要利用之前算过的L[i]或R[i],就可不必模拟全部的骨牌效应,这样路径压缩了,就可以在O(n)的时间预处理出来。

之后的DP,f[i]的含义仍和上面相同,不同之处在于存下,最近用了f[j]的值是在那个j,只要这个j在l[i]到i的范围内,就可以用来更新f[i]

{{{
#include<cstdio>
#include<algorithm>
using namespace std;
typedef unsigned u32;
#define X first
#define H second
pair<u32,u32> b[102400];
u32 r[102400],l[102400],f[102400],m[102400];
int main(){
    for(u32 n;~scanf("%u",&n);){
        for(size_t i=0;i<n;++i){
            scanf("%u%u",&b[i].X,&b[i].H);
            b[i].X+=1000000000u;
        }
        sort(b,b+n);
        for(u32 i=n,k;i--;r[i]=k)
            for(k=i;k+1<n&&b[i].X+b[i].H>=b[k+1].X;k=r[k+1]); 
        for(u32 i=0,k;i<n;l[i++]=k)
            for(k=i;k&&b[i].X-b[i].H<=b[k-1].X;k=l[k-1]);
        for(u32 i=0;i<=n;++i){f[i]=i;m[i]=0;}
        for(u32 i=0;i<n;++i){
            if(f[i]<f[r[i]+1])f[r[i]+1]=f[i]+1;
            u32 j=l[i],k=f[i];
            for(;k && m[k-1]>=j;--k);
            if(k<f[i+1])f[i+1]=k+1;
            m[f[i+1]]=i+1;
        }
        printf("%u\n",f[n]);
    }
    return 0;
}
}}}

题意:一线排列的积木,每个积木有一定的高度,每个可以沿一个方向推倒,会引起连锁反应,问最少碰倒多少个可以推到全部。

按x坐标排序是必不可少的。

先预处理出,两个方向推到一个,可以推到到对应方向的最远位置,l[i],r[i],最坏O(n2)可以预处理出来,

于是很容易想出O(n2)的DP,f[i]表示从左至右推倒到i最少要碰倒几个. f[i] = min(f[l[i]-1]+1,f[i]),再以之f[i]+1更新f[j, j from i+1 to r[i]].

这样可以n2解决这个问题,同时预处理和DP的过程都可以用线段树优化到O(n log n),这样就可以在O(n log n)解决这个问题。

实际上,不算排序,可以用O(n)的方法,解决它,实现也比较简单。

L[i]和R[i]意义和上面一样,需要预处理,

注意到,每次推到如果引起连锁反应,必然只需要找到它只靠自己能推的最远的位置,剩下的要靠它推倒的积木的连锁反应来延续,

于是只要利用之前算过的L[i]或R[i],就可不必模拟全部的骨牌效应,这样路径压缩了,就可以在O(n)的时间预处理出来。

之后的DP,f[i]的含义仍和上面相同,不同之处在于存下,最近用了f[j]的值是在那个j,只要这个j在l[i]到i的范围内,就可以用来更新f[i]

#include<cstdio>
#include<algorithm>
using namespace std;
typedef unsigned u32;
#define X first
#define H second
pair<u32,u32> b[102400];
u32 r[102400],l[102400],f[102400],m[102400];
int main(){
    for(u32 n;~scanf("%u",&n);){
        for(size_t i=0;i<n;++i){
            scanf("%u%u",&b[i].X,&b[i].H);
            b[i].X+=1000000000u;
        }
        sort(b,b+n);
        for(u32 i=n,k;i--;r[i]=k)
            for(k=i;k+1<n&&b[i].X+b[i].H>=b[k+1].X;k=r[k+1]); 
        for(u32 i=0,k;i<n;l[i++]=k)
            for(k=i;k&&b[i].X-b[i].H<=b[k-1].X;k=l[k-1]);
        for(u32 i=0;i<=n;++i){f[i]=i;m[i]=0;}
        for(u32 i=0;i<n;++i){
            if(f[i]<f[r[i]+1])f[r[i]+1]=f[i]+1;
            u32 j=l[i],k=f[i];
            for(;k && m[k-1]>=j;--k);
            if(k<f[i+1])f[i+1]=k+1;
            m[f[i+1]]=i+1;
        }
        printf("%u\n",f[n]);
    }
    return 0;
}