NOIP 模拟 1007

粉刷宿舍

在一个网格图中,有n列需要粉刷,且在第i列中未被粉刷的部分是从地面向上的长为ai的连续段。

可以将刷子置于某个格子从上下左右选一个方向刷。但是不能触碰不应该刷的地方。

求最少多少次操作刷完。

1≤𝑛≤500000,且 0≤𝑎𝑖≤109

题解

想到NOIP2018 day1 T1,在一个区间找一个最小值,让整个区间减去这个值,然后就分成两个区间,一直递归。

但是这是n^2的(我也不知道noip咋过的)

先不考虑竖着涂的话,思考我们时间花在了哪里:每次找最小值都是for一遍。

考虑到递归过程中在同一区间的数一定被减了相等的数,所以相对大小不会改变,所以用st表维护最小值位置。(我才不会说一开始线段树挂了才想的st表)

st表查询是O(1)的,很舒服。

一共有n个数,每向下搜一次就会少一个数,所以区间个数是O(n)的,所以递归是O(n)的,不过st表的建立是O(nlogn)的。(应该是这样的吧)

现在考虑竖着涂,对于[l,r]的区间,竖着涂的次数为r-l+1,所以只要取个min就好了。

还有O(n)的单调栈我就不会了。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
#define re register
const int maxn=500005;
int n,a[maxn];
int lg[maxn],st[maxn][21];

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

ll min(ll x,ll y){return x<y ? x : y ;}

void rmq(){
    lg[0]=-1;
    for(re int i=1;i<=n;++i) lg[i]=lg[i>>1]+1;
    for(re int i=1;i<=n;++i) st[i][0]=i;
    for(re int j=1;(1<<j)<=n;++j)
     for(re int i=1;i+(1<<j)-1<=n;++i)
      st[i][j]= a[st[i][j-1]] < a[st[i+(1<<(j-1))][j-1]] ? st[i][j-1] : st[i+(1<<(j-1))][j-1];
}

int ask(int l,int r){
    int o=lg[r-l+1];
  return a[st[l][o]]<a[st[r-(1<<o)+1][o]] ? st[l][o] : st[r-(1<<o)+1][o];
}

ll dfs(int l,int r,int val){
    if(l>r) return 0;
    int pos=ask(l,r),x=a[pos]-val;
    if(x>r-l+1) return r-l+1;
    //if(x>lim) return lim+1;
    int ans=dfs(l,pos-1,val+x)+x;
    if(ans>r-l+1) return  r-l+1;
    ans+=dfs(pos+1,r,val+x);
    if(ans>r-l+1) return r-l+1;
    return ans;
}

int main(){
    //freopen("paint3.in","r",stdin);
    //freopen("paint.out","w",stdout);
    read(n);
    for(re int i=1;i<=n;++i) read(a[i]);
    rmq();
    printf("%lld",dfs(1,n,0));
    return 0;
}
paint

 


入学考试

定义反封闭数集S(正整数):1属于S,对于其中不为1的数m都可以写成S中两个元素的和(可以相同)

求一个最大数是2^n-1的反封闭数集,同时元素个数不超过k

n<=1000,k<=1014

题解

这题有点玄。

就是想用最少的数凑出2^n-1。

考虑这样一个过程(by sxk):

          1

         10

         11

        110

       1100

       1111

      11110

     111100

    1111000

   11110000

   11111111

能懂?就是先不停左移(就是自加),能使得成为全1就再加。

不知道为什么增长很快。

模拟就是了,写的丑。最后用全1序列凑出n

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxn=1205;
int n,k,lg[maxn];
int tot,pos[maxn];
int num[maxn],top;
struct big{
    int len;
    bool a[maxn];
    void print(){for(int i=n;i;i--) printf("%d",a[i]);putchar(10);}
    big(){len=0;memset(a,false,sizeof(a));}
}f[maxn];

void init(int x){
    while(x){
        num[++top]=x&1;
        x>>=1;
    }
    top--;
}

int main(){
    freopen("exam.in","r",stdin);
    freopen("exam.out","w",stdout);
    scanf("%d%d",&n,&k);
    init(n);
    lg[0]=-1;
    for(int i=1;i<=n;i++) lg[i]=lg[i>>1]+1;
    for(int i=1,p=1;p<=1<<lg[n];i+=p+1,p<<=1){//先弄出全1的序列 ,到n的二进制最大拆分为止 
        pos[p]=i;f[i].len=p;
        for(int j=1;j<=p;j++) f[i].a[j]=1;
        tot=i;
    }
    for(int i=2;i<=tot;i++)//左移 
     if(!f[i].len){
          f[i].len=f[i-1].len+1;
          for(int j=2;j<=f[i].len;j++) f[i].a[j]=f[i-1].a[j-1];
          f[i].a[0]=0;
     }
    for(int i=tot+1;f[i-1].len<n;i++,tot++){//与n的长度补齐 
        f[i].len=f[i-1].len+1;
         for(int j=2;j<=f[i].len;j++) f[i].a[j]=f[i-1].a[j-1];
         f[i].a[0]=0;
    }
    for(int now=1<<lg[n];;top--){//凑出n 
        while(!num[top]&&top) top--;
        if(!top) break;
        now+=1<<(top-1);
        ++tot;
        for(int i=n;i+now>n;i--) f[tot].a[i]=1;
    }
    printf("%d\n",tot);
    for(int i=1;i<=tot;i++) f[i].print();
    
}
exam

 


选礼物

有n组礼物,每组礼物有ai个,一组礼物只要有人收到就得到bi的满意度,价格为ci,有些礼物只能在买了di后才能买。有m个人,每个人至少一个礼物。

求满意度与花费的最大比值。

𝑛≤1000,𝑚≤1000,0≤𝑑𝑖≤𝑛,1≤𝑎𝑖≤𝑚,1≤𝑏𝑖,𝑐𝑖≤32768。

题解

很明显是01分数规划,而且关系构成一个树形结构,跑背包就好了。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define re register
const int maxn=1005;
const double eps=1e-6;
const double oo=9999999999;
int n,m;
int cnt,head[maxn],size[maxn];
int a[maxn],b[maxn],c[maxn];
double val[maxn],f[maxn][maxn],tmp[maxn];
struct edge{
    int x,y,next;
}e[maxn<<1];

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

void add(int x,int y){
    e[++cnt]=(edge){x,y,head[x]};
    head[x]=cnt;
}

int min(int x,int y){return x<y ? x : y ;}

void dfs(int x){
    size[x]=a[x];
    f[x][0]=0;
    for(re int i=1;i<=min(size[x],m);++i) f[x][i]=val[x];
    for(re int i=head[x];i;i=e[i].next){
        int y=e[i].y;
        dfs(y);
        for(re int j=1;j<=min(size[x]+size[y],m);++j) tmp[j]=-oo;
        for(re int j=1;j<=min(size[x],m);++j)
         for(re int k=0;k<=min(size[y],m);++k){ 
            tmp[min(m,j+k)]=max(tmp[min(j+k,m)],f[x][j]+f[y][k]);
         }
        size[x]+=size[y];
        for(re int j=1;j<=min(size[x],m);++j) f[x][j]=tmp[j];
    }
}

void plana(){
    m++;a[0]=1;
    double l=0,r=32769;
    while(r-l>=eps){
        double mid=(l+r)*0.5;
        for(re int i=1;i<=n;++i) val[i]=1.0*b[i]-mid*c[i];
        dfs(0);
        if(f[0][m]>0) l=mid;
        else r=mid;
    }
    printf("%.3lf",l);
}

int main(){
    freopen("gift11.in","r",stdin);
    freopen("gift.out","w",stdout);
    read(n);read(m);
    bool opt=false;
    for(re int i=1;i<=n;++i){
        read(a[i]);read(b[i]);read(c[i]);
        int x;read(x);
        add(x,i);
    }
    plana();
}
/*
5 10 
1 8695 30594 0 
7 9390 3109 1 
8 30425 17108 1 
6 24713 14066 1 
6 365 18488 2
*/
50

不过直接在树上跑的话,因为子树和可能达到m,所以一次dfs会变成(n*m*m)(也可能是我写错了)

去找题解的时候发现了另一种方法:在dfs序上跑。

好像这两个都是常用的方法。

这道题用后者就可以A了,不过为什么复杂度会降下来没怎么想清楚。写起来还比较简单,感觉是板子。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define re register
const int maxn=1005;
const double eps=1e-6;
const double oo=99999999999;
int n,m;
int cnt,head[maxn],size[maxn],id[maxn],mp[maxn];
int a[maxn],b[maxn],c[maxn];
double val[maxn],f[maxn][maxn];
struct edge{
    int x,y,next;
}e[maxn<<1];

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

void add(int x,int y){
    e[++cnt]=(edge){x,y,head[x]};
    head[x]=cnt;
}

int min(int x,int y){return x<y ? x : y ;}

void dfs(int x){
    size[x]=1;id[x]=++cnt;mp[cnt]=x;
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].y;
        dfs(y);
        size[x]+=size[y];
    }
}

bool check(){
    for(int i=1;i<=m;i++) f[n+2][i]=-oo;
    f[n+2][0]=0;
    for(int i=n+1;i;i--){//有0节点 
        int x=mp[i];
        double tmp=-oo;
        for(int j=max(0,m-a[x]);j<=m;j++) tmp=max(tmp,f[i+1][j]);
        f[i][m]=max(tmp+val[x],f[i+size[x]][m]);//特殊处理,因为至少m个 
        for(int j=0;j<m;j++){
            if(j>=a[x]) f[i][j]=max(f[i+1][j-a[x]]+val[x],f[i+size[x]][j]);//选和不选 
            else f[i][j]=f[i+size[x]][j];
        }
    }
    return f[1][m]>-eps;
}

void plana(){
    dfs(0);
    double l=0,r=32769;
    while(r-l>=eps){
        double mid=(l+r)*0.5;
        for(re int i=1;i<=n;++i) val[i]=1.0*b[i]-mid*c[i];
        if(check()) l=mid;
        else r=mid;
    }
    printf("%.3lf",l);
}

int main(){
    freopen("gift.in","r",stdin);
    freopen("gift.out","w",stdout);
    read(n);read(m);
    m++;
    for(re int i=1;i<=n;++i){
        read(a[i]);read(b[i]);read(c[i]);
        int x;read(x);
        add(x,i);
    }
    cnt=0;
    plana();
}
/*
5 10 
1 8695 30594 0 
7 9390 3109 1 
8 30425 17108 1 
6 24713 14066 1 
6 365 18488 2
*/
100

给几个看到还行的讲解,关于问题的。

1

2

posted @ 2019-10-07 20:58  _JSQ  阅读(135)  评论(0编辑  收藏  举报