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; }
入学考试
定义反封闭数集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(); }
选礼物
有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 */
不过直接在树上跑的话,因为子树和可能达到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 */
给几个看到还行的讲解,关于问题的。