[APIO2009]
来自FallDream的博客,不经允许,请勿转载,谢谢。
------------------------------------------------------
1.Oil
给定一个n*m的矩阵,你要从中选出恰好3个k*k的不想交的矩阵,并使得矩阵数字的和最大。n,m<=1500
题解:很显然,三个矩阵只有两种排布方案:1)先横着或者竖着割成两段,然后在其中一边再分成两段。3)横着或者竖着分成三段。
所以我们用A[i][j]表示前i行j列选一个的最大值,其他三个角同理,然后处理一下横着和竖着的一个区间的最大值就可以啦。
#include<iostream> #include<cstdio> #define MN 1505 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } int n,m,k,ans=0; int f[4][MN][MN]; int g[MN][MN],h[MN],e[MN][MN],d[MN][MN],c[MN]; int s[MN][MN]; inline int calc(int x,int y){return (x<=n&&y<=m&&x>=k&&y>=k)?(s[x][y]-s[x-k][y]-s[x][y-k]+s[x-k][y-k]):0;} int main() { //freopen("test0.in","r",stdin); n=read();m=read();k=read(); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&s[i][j]); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) s[i][j]=s[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1]; for(int i=k;i<=n;i++) for(int j=k;j<=m;j++) g[i][j]=calc(i,j),h[i]=max(h[i],g[i][j]),c[j]=max(c[j],g[i][j]); for(int i=k;i<=n;i++) for(int j=k;j<=m;j++) f[0][i][j]=max(f[0][i][j-1],max(f[0][i-1][j],g[i][j])); for(int i=k;i<=n;i++) for(int j=m-k+1;j;j--) f[1][i][j]=max(f[1][i][j+1],max(f[1][i-1][j],g[i][j+k-1])); for(int i=n-k+1;i;i--) for(int j=k;j<=m;j++) f[2][i][j]=max(f[2][i][j-1],max(f[2][i+1][j],g[i+k-1][j])); for(int i=n-k+1;i;i--) for(int j=m-k+1;j;j--) f[3][i][j]=max(f[3][i][j+1],max(f[3][i+1][j],g[i+k-1][j+k-1])); for(int i=k;i<=n;i++) { int mx=0; for(int j=i;j<=n;j++) mx=max(mx,h[j]),e[i][j]=mx; } for(int i=k;i<=m;i++) { int mx=0; for(int j=i;j<=m;j++) mx=max(mx,c[j]),d[i][j]=mx; } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { if(i+k<=n&&i>=k&&j>=k&&j+k<=m)ans=max(ans,f[0][i][j]+f[1][i][j+1]+e[i+k][n]); if(i+k-1<=n&&i>k&&j>k&&j+k-1<=m) ans=max(ans,f[2][i][j-1]+f[3][i][j]+e[k][i-1]); if(i+k<=n&&i>=k&&j>=k&&j+k<=m) ans=max(ans,f[0][i][j]+f[2][i+1][j]+d[j+k][m]); if(i+k<=n&&i>=k&&j>k&&j+k-1<=m)ans=max(ans,f[1][i][j]+f[3][i+1][j]+d[k][j-1]); } for(int i=k+1;i<=n;i++) for(int j=i+k-1;j+k<=n;j++) ans=max(ans,e[k][i-1]+e[i+k-1][j]+e[j+k][n]); for(int i=k+1;i<=m;i++) for(int j=i+k-1;j+k<=m;j++) ans=max(ans,d[k][i-1]+d[i+k-1][j]+d[j+k][m]); cout<<ans; return 0; }
B.会议中心
活动安排问题,但是要求选的活动最多的情况下字典序最小。 n<=200000
题解:
按照字典序从小到大,当一个线段[l,r]插入时,找到它的前驱后继[ll,rr]。只有当[ll,l)的方案数+(r,rr]的方案数+1恰好等于[ll,rr]的方案数的时候,它才能成为最优解,那么就把它选入答案。
怎样快速算出一个区间答案呢?倍增。我们离散后,用f[i][k]表示第i个点向后走2^k个区间最少到达哪里,预处理好后每次查询只要log.
复杂度nlogn
如果用set,代码就很短啦,但是我不想用set,练习一下平衡树吧。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define INF 2000000000 #define MN 200000 #define ML 18 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } int fa[MN*4+5],c[MN*4+5][2],size[MN*4+5],nn[MN*4+5],q[MN*4+5],top,sign=0,rt=0,cc=0; int f[MN*2+5][ML+2]; int n,l[MN+5],r[MN+5],l2[MN*2+5],tot=0,cnt=1; struct cus{int l,r,num;}s[MN+5]; bool cmp(cus x,cus y){return x.r<y.r||(x.r==y.r&&x.l>y.l);} void ins(int&x,int k,int last) { if(!x){x=++cc;fa[x]=last;nn[x]=k;size[x]=1;return;} if(k<=nn[x]) ins(c[x][0],k,x); else ins(c[x][1],k,x); size[x]=size[c[x][0]]+size[c[x][1]]+1; if(max(size[c[x][0]],size[c[x][1]])>0.7*size[x]) sign=x; } void dfs(int x) { if(c[x][0]) dfs(c[x][0]); q[++top]=x; if(c[x][1]) dfs(c[x][1]); fa[x]=c[x][0]=c[x][1]=0; } void build(int x,int l,int r,int last) { if(l>r){x=0;return;}int mid=l+r>>1; x=q[mid];fa[x]=last; build(c[x][0],l,mid-1,x); build(c[x][1],mid+1,r,x); size[x]=size[c[x][0]]+size[c[x][1]]+1; } void rebuild() { int y=fa[sign];top=0;dfs(sign); if(!y) build(rt,1,top,0); else build(c[y][c[y][1]==sign],1,top,y); sign=0; } int query(int x,int rk) { if(!x) return 0; if(nn[x]<=rk) return size[c[x][0]]+1+query(c[x][1],rk); else return query(c[x][0],rk); } int ask_before(int x,int rk) { if(!x) return 0;int q; if(nn[x]<rk) return (q=ask_before(c[x][1],rk))?q:nn[x]; else return ask_before(c[x][0],rk); } int ask_after(int x,int rk) { if(!x) return 0;int q; if(nn[x]>rk) return (q=ask_after(c[x][0],rk))?q:nn[x]; else return ask_after(c[x][1],rk); } int calc(int l,int r) { int sum=0; for(int i=ML;i>=0;i--) if(f[l][i]<=r+1) l=f[l][i],sum+=1<<i; // cout<<"calc"<<l<<" "<<r<<" "<<sum<<endl; return sum; } inline bool check(int l,int r) { int x=query(rt,l-1),y=query(rt,r); // cout<<"check"<<l<<" "<<r<<" "<<x<<" "<<y<<endl; return query(rt,l-1)==query(rt,r)&&(y&1); } int main() { n=read(); for(int i=1;i<=n;i++) s[i].l=l[i]=read(),s[i].r=r[i]=read(),s[i].num=i; for(int i=1;i<=n;i++) l2[++tot]=s[i].l,l2[++tot]=s[i].r; sort(l2+1,l2+tot+1); for(int i=2;i<=tot;i++) if(l2[i]!=l2[i-1]) l2[++cnt]=l2[i]; memset(f,127,sizeof(f));; for(int i=1;i<=n;i++) { l[i]=lower_bound(l2,l2+cnt+1,l[i])-l2; r[i]=lower_bound(l2,l2+cnt+1,r[i])-l2; f[l[i]][0]=min(f[l[i]][0],r[i]+1); } for(int i=cnt;i>=0;i--) { f[i][0]=min(f[i][0],f[i+1][0]); for(int k=1;k<=ML;k++) if(f[i][k-1]<INF) f[i][k]=f[f[i][k-1]][k-1]; // for(int k=0;k<=4;k++) // cout<<i<<" "<<k<<" "<<f[i][k]<<endl; } ins(rt,0,0);ins(rt,INF,0); printf("%d\n",calc(1,INF));bool yes=false; for(int i=1;i<=n;i++) { if(!check(l[i],r[i])) continue; int ll=ask_before(rt,l[i]),rr=ask_after(rt,r[i]); if(calc(ll,l[i]-1)+calc(r[i]+1,rr)+1!=calc(ll,rr)) continue; ins(rt,l[i],0);ins(rt,r[i],0); if(yes)printf(" ");else yes=true; printf("%d",i); } return 0; }
C.Atm
这道题是我以前做的.....
http://www.cnblogs.com/FallDream/p/bzoj1179.html
FallDream代表秋之国向您问好!
欢迎您来我的博客www.cnblogs.com/FallDream