[BZOJ1177][BZOJ1178][BZOJ1179]APIO2009解题报告
抱着好奇心态去开始做APIO的往年试题感受一下难度
Oil
Description
采油区域 Siruseri政府决定将石油资源丰富的Navalur省的土地拍卖给私人承包商以建立油井。被拍卖的整块土地为一个矩形区域,被划分为M×N个小块。 Siruseri地质调查局有关于Navalur土地石油储量的估测数据。这些数据表示为M×N个非负整数,即对每一小块土地石油储量的估计值。 为了避免出现垄断,政府规定每一个承包商只能承包一个由K×K块相连的土地构成的正方形区域。 AoE石油联合公司由三个承包商组成,他们想选择三块互不相交的K×K的区域使得总的收益最大。 例如,假设石油储量的估计值如下: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 8 8 8 8 8 1 1 1 1 8 8 8 8 8 1 1 1 1 8 8 8 8 8 1 1 1 1 1 1 1 8 8 8 1 1 1 1 1 1 1 1 8 8 8 1 1 1 1 1 1 9 9 9 1 1 1 1 1 1 9 9 9 如果K = 2, AoE公司可以承包的区域的石油储量总和为100, 如果K = 3, AoE公司可以承包的区域的石油储量总和为208。 AoE公司雇佣你来写一个程序,帮助计算出他们可以承包的区域的石油储量之和的最大值。
首先由于是一个个正方形,所以很容易看出最终每块油田所在的区域是通过一些水平或竖直的线隔开的
并且这些线在它所在的区域内是到达边界的 这道题三个油田也就是最终只可能为下面六种情况
其中右边的两列看起来比较好处理
我们只要先预处理左上左下右上右下四个方向最大答案的前缀和
然后再枚举水平和竖直线的位置
稍加处理就可以得出答案
左边的一列以两条水平线为例
我们枚举一条水平线之后,枚举另一条在它下面的水平线
这条水平线每向下移一行,中间部分也就多了一行
所以我们可以预处理出每行每列的最大答案
在枚举第二条线的时候维护中间部分答案的最大值
上下部分用前缀和来计算
在写代码的过程中,由于我统计的是以(x,y)为左上角的边长为k的矩形的答案
所以前缀和部分用到的时候需要注意哪些地方需要-k+1,哪些地方是限制住的
时间复杂度O(nm)
/**************************************************************
Problem: 1177
User: mjy0724
Language: C++
Result: Accepted
Time:6128 ms
Memory:54256 kb
****************************************************************/
#include<cstdio>
#include<cstdlib>
#include<cstring>
const int maxn = ;
int a[maxn][maxn],map[maxn][maxn],lu[maxn][maxn],ru[maxn][maxn],ld[maxn][maxn],rd[maxn][maxn],h[maxn],v[maxn];
int max(int a,int b){if (a>b) return a;else return b;}
int main(){
int n,m,k,ans=;
scanf("%d%d%d",&n,&m,&k);
for (int i=;i<=n;i++) for (int j=;j<=m;j++) scanf("%d",&map[i][j]);
for (int i=;i<=n;i++) for (int j=;j<=m;j++) map[i][j]+=map[i-][j];
for (int i=;i<=n;i++) for (int j=;j<=m;j++) map[i][j]+=map[i][j-];
for (int i=;i<=n-k+;i++) for (int j=;j<=m-k+;j++) a[i][j]=map[i+k-][j+k-]-map[i-][j+k-]-map[i+k-][j-]+map[i-][j-];
for (int i=;i<=n-k+;i++) for (int j=;j<=m-k+;j++) lu[i][j]=max(max(lu[i-][j],lu[i][j-]),a[i][j]);
for (int i=;i<=n-k+;i++) for (int j=m-k+;j>;j--) ru[i][j]=max(max(ru[i-][j],ru[i][j+]),a[i][j]);
for (int i=n-k+;i>;i--) for (int j=;j<=m-k+;j++) ld[i][j]=max(max(ld[i+][j],ld[i][j-]),a[i][j]);
for (int i=n-k+;i>;i--) for (int j=m-k+;j>;j--) rd[i][j]=max(max(rd[i+][j],rd[i][j+]),a[i][j]);
for (int i=;i<=n;i++) for (int j=;j<=m;j++) h[i]=max(h[i],a[i][j]),v[j]=max(v[j],a[i][j]);
for (int i=k;i<=n-*k;i++){
int mx = ;
for (int j=i+k;j<=n-k;j++){
mx = max(mx,h[j-k+]);
ans = max(ans,lu[i-k+][m-k+]+mx+ld[j+][m-k+]);
}
}
for (int i=k;i<=n-k;i++)
for (int j=k;j<=m-k;j++) ans=max(ans,lu[i-k+][j-k+]+ru[i-k+][j+]+ld[i+][m-k+]);
for (int i=k;i<=n-k;i++)
for (int j=k;j<=m-k;j++) ans=max(ans,lu[i-k+][m-k+]+ld[i+][j-k+]+rd[i+][j+]);
for (int i=k;i<=m-*k;i++){
int mx=;
for (int j=i+k;j<=m-k;j++){
mx = max(mx,v[j-k+]);
ans = max(ans,lu[n-k+][i-k+]+mx+ru[n-k+][j+]);
}
}
for (int i=k;i<=n-k;i++)
for (int j=k;j<=m-k;j++) ans=max(ans,lu[i-k+][j-k+]+ru[n-k+][j+]+ld[i+][j-k+]);
for (int i=k;i<=n-k;i++)
for (int j=k;j<=m-k;j++) ans=max(ans,lu[n-k+][j-k+]+ru[i-k+][j+]+rd[i+][j+]);
printf("%d\n",ans);
return ;
}
[BZOJ1178]CONVENTION会议中心
Description
Siruseri政府建造了一座新的会议中心。许多公司对租借会议中心的会堂很感兴趣,他们希望能够在里面举行会议。 对于一个客户而言,仅当在开会时能够独自占用整个会堂,他才会租借会堂。会议中心的销售主管认为:最好的策略应该是将会堂租借给尽可能多的客户。显然,有可能存在不止一种满足要求的策略。 例如下面的例子。总共有4个公司。他们对租借会堂发出了请求,并提出了他们所需占用会堂的起止日期(如下表所示)。 开始日期 结束日期 公司1 4 9 公司2 9 11 公司3 13 19 公司4 10 17 上例中,最多将会堂租借给两家公司。租借策略分别是租给公司1和公司3,或是公司2和公司3,也可以是公司1和公司4。注意会议中心一天最多租借给一个公司,所以公司1和公司2不能同时租借会议中心,因为他们在第九天重合了。 销售主管为了公平起见,决定按照如下的程序来确定选择何种租借策略:首先,将租借给客户数量最多的策略作为候选,将所有的公司按照他们发出请求的顺序编号。对于候选策略,将策略中的每家公司的编号按升序排列。最后,选出其中字典序最小1的候选策略作为最终的策略。 例中,会堂最终将被租借给公司1和公司3:3个候选策略是{(1,3),(2,3),(1,4)}。而在字典序中(1,3)<(1,4)<(2,3)。 你的任务是帮助销售主管确定应该将会堂租借给哪些公司。
这道题我觉得非常有意思,用一些简单的算法拼凑出了一个很好的解题思路,虽然代码复杂度略高
首先第一问的答案很显然...直接贪心就可以出解
我们可以考虑两段时间区间,如果其中一段包含另一段,那么显然这条长的线段是没有用的
因为在每条线段对答案的贡献都=1的情况下,选这条短的能为其他线段的摆放留有更多空间
我们把所有的这种不优秀的线段都删除掉以后,将它们按左端点从小到大排序之后
会发现右端点也是升序,因为否则就会出现包含的情况
可以考虑取了某条线段i以后,实际上下一条取哪条线段已经确定了
因为在可以取的范围(左端点大于线段i的右端点)的线段集合中,我们肯定取那个右端点最小的
由于我们上面得出左端点与右端点同时上升,二分找出第一个左端点大于线段i右端点的线段就是下一条要取的线段
这样一来好像忽然明白了:其实只要确定了起点,整个要取的线段序列都是确定的
我们可以用倍增来储存第i条线段接下去要取的第2^j条线段
直接按照题目的要求来,从小到大去试
只要当前线段能够放下(即在此之前的线段没有覆盖到这条线段所在的位置)
咦那为什么不能让之前的移一下呢?我们的顺序是从小到大,人家的优先级比你要高
所以这样做有一个比较好的性质:确定了的就不能修改
另外,还要保证放下了之后最优解不变,我们设当前线段所在的空白位置向左右延伸到的最远处分别是lx,rx,当前线段(l,r)
那么ans(lx,rx)=ans(l,lx-1)+ans(r+1,rx)+1才能够保证最优解不变
那么获取答案的函数ans如何求得呢?
上上段我们处理好了倍增的数组,ans即求一个给定起点和终点的线段区间的答案
直接用倍增处理就可以了
/**************************************************************
Problem: 1178
User: mjy0724
Language: C++
Result: Accepted
Time:5804 ms
Memory:85588 kb
****************************************************************/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
const int maxn=,INF=;
struct point{
int x,y;
}a[maxn],c[maxn],p[maxn];
struct tree{
int l,r,lx,rx,cov;
}tr[maxn*];
int n,tot,fa[maxn][],ans,as[maxn],d[maxn],e[maxn];
bool vis[maxn];
char s[];
int max(int a,int b){
if (a>b) return a;
else return b;
}
int min(int a,int b){
if (a<b) return a;
else return b;
}
void sortf(int l,int r){
int i=l,j=r,mid=(l+r) >> ,midx=a[mid].x,midy=a[mid].y;
do{
while (i<r&&((a[i].x>midx)||(a[i].x==midx&&a[i].y<midy))) i++;
while (l<j&&((a[j].x<midx)||(a[j].x==midx&&a[j].y>midy))) j--;
if (i<=j){
a[]=a[i];a[i]=a[j];a[j]=a[];
i++;j--;
}
}while (i<=j);
if (i<r) sortf(i,r);
if (l<j) sortf(l,j);
}
void sortg(int l,int r){
int i=l,j=r,mid=p[(l+r) >> ].x;
do{
while (i<r&&p[i].x<mid) i++;
while (l<j&&p[j].x>mid) j--;
if (i<=j){
p[]=p[i];p[i]=p[j];p[j]=p[];
i++;j--;
}
}while (i<=j);
if (i<r) sortg(i,r);
if (l<j) sortg(l,j);
}
int find(int x){
int ans=,l=,r=tot;
while (l<=r){
int mid=(l+r) >> ;
if (p[mid].x>=x){
ans=mid;r=mid-;
}else l=mid+;
}
return ans;
}
void solve(){
int minr=INF;
memset(vis,true,sizeof(vis));
int j;
for (int i=;i<=n;i=j){
for (j=i;a[i].x==a[j].x&&j<=n;j++){
if (minr<a[j].y) vis[j]=false;
if (a[j].y<minr) minr=a[j].y;
}
}
tot=;
for (int i=;i<=n;i++) if (vis[i]) p[++tot]=a[i];
sortg(,tot);
for (int i=;i<=tot;i++) fa[i][]=find(p[i].y+);
for (int j=;( << j)<=n;j++)
for (int i=;i<=tot;i++) fa[i][j]=fa[fa[i][j-]][j-];
}
void build(int p,int l,int r){
tr[p].l=l;tr[p].r=r;tr[p].lx=INF;tr[p].rx=-INF;tr[p].cov=;
if (l!=r){
int mid = (l+r) >> ;
build(p<<,l,mid);build((p<<)+,mid+,r);
}
}
void sorth(int l,int r){
int i=l,j=r,mid=d[(l+r) >> ];
do{
while (i<r&&d[i]<mid) i++;
while (l<j&&d[j]>mid) j--;
if (i<=j){
int tem=d[i];d[i]=d[j];d[j]=tem;
i++;j--;
}
}while (i<=j);
if (i<r) sorth(i,r);
if (l<j) sorth(l,j);
}
void ls(){
for (int i=;i<=n;i++){
d[++d[]]=a[i].x;d[++d[]]=a[i].y;
}
sorth(,d[]);
int j;
for (int i=;i<=d[];i=j){
e[++e[]]=d[i];j=i+;
while (j<=d[]&&d[i]==d[j]) j++;
}
}
void insert(int p,int l,int r){
if (tr[p].cov) {tr[p].lx=tr[p].l,tr[p].rx=tr[p].r;}
if (tr[p].l==l&&tr[p].r==r){
tr[p].cov=;tr[p].lx=tr[p].l;tr[p].rx=tr[p].r;
return;
}
int mid = (tr[p].l+tr[p].r) >> ;
if (tr[p].cov) tr[p<<].cov=,tr[(p<<)+].cov=;
if (r<=mid) insert(p<<,l,r);else
if (l>mid) insert((p<<)+,l,r);else{
insert(p<<,l,mid);insert((p<<)+,mid+,r);
}
tr[p].lx=min(tr[p<<].lx,tr[(p<<)+].lx);
tr[p].rx=max(tr[p<<].rx,tr[(p<<)+].rx);
}
int getnum(int x){
int l=,r=e[];
while (l<=r){
int mid=(l+r) >> ;
if (e[mid]==x) return mid;
if (x<e[mid]) r=mid-;else l=mid+;
}
}
int getl(int p,int l,int r){
if (tr[p].cov){tr[p].lx=l;tr[p].rx=r;}
if (tr[p].l==l&&tr[p].r==r) return tr[p].lx;
int mid=(tr[p].l+tr[p].r) >> ;
if (tr[p].cov) tr[p<<].cov=,tr[(p<<)+].cov=;
if (r<=mid) return getl(p<<,l,r);else
if (l>mid) return getl((p<<)+,l,r); else
return min(getl(p<<,l,mid),getl((p<<)+,mid+,r));
}
int getr(int p,int l,int r){
if (tr[p].cov){tr[p].lx=l;tr[p].rx=r;}
if (tr[p].l==l&&tr[p].r==r) return tr[p].rx;
int mid=(tr[p].l+tr[p].r)>>;
if (tr[p].cov) tr[p<<].cov=,tr[(p<<)+].cov=;
if (r<=mid) return getr(p<<,l,r);else
if (l>mid) return getr((p<<)+,l,r);else
return max(getr(p<<,l,mid),getr((p<<)+,mid+,r));
}
int query(int x,int y){
if (x<||y<||x>e[]||y>e[]) return ;
int s=-,l=,r=tot;
while (l<=r){
int mid=(l+r) >> ;
if (p[mid].x>=e[x]) s=mid,r=mid-;else l=mid+;
}
int t=-;
l=;r=tot;
while (l<=r){
int mid=(l+r) >> ;
if (p[mid].y<=e[y]) t=mid,l=mid+;else r=mid-;
}
if (s==-||t==-||s>t) return ;
int ans=,i=log(tot)/log();
while (s<t&&i>=){
while (s<t&&fa[s][i]!=&&fa[s][i]<=t) s=fa[s][i],ans+=<<i;
i--;
}
return ans;
}
int main(){
scanf("%d",&n);
for (int i=;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y),gets(s);
for (int i=;i<=n;i++) c[i].x=a[i].x,c[i].y=a[i].y;
ls();
sortf(,n);
solve();
build(,,e[]+);
insert(,,);insert(,e[]+,e[]+);
for (int i=;i<=n;i++) c[i].x=getnum(c[i].x),c[i].y=getnum(c[i].y);
for (int i=;i<=n;i++) if (getl(,c[i].x,c[i].y)==INF&&getr(,c[i].x,c[i].y)==-INF){
int ll=getr(,,c[i].x-),rr=getl(,c[i].y+,e[]+);
if (query(ll+,rr-)==query(ll+,c[i].x-)+query(c[i].y+,rr-)+) {
as[++ans]=i;insert(,c[i].x,c[i].y);
}
}
printf("%d\n",ans);
for (int i=;i<=ans;i++) printf("%d ",as[i]);
return ;
}
[BZOJ1179]Atm
Description
这大概是这套题目中最简单的一道了...
首先我们可以看出,题目中说的,可以经过一条边多次其实就是要处理环的情况
取款机里的钱都是非负整数,能拿的没有理由不要
因此如果进入了一个环,就可以把环中所有的钱都取光,然后从任意一个点出去
思路已经很明确了,tarjan缩点,同时累计每一个环的现金总数和是否存在一个酒吧
缩完点的图我们重新连上边(自环不连),就变成了一个有向无环图
我们可以用拓扑序DP来解决这个问题
对于给定的起点,这样类似的题目之前也做过
我的做法是先从起点开始dfs一次,重新统计入度
然后再进行DP,最后在那些有酒吧坐落的环或点答案中挑出一个最大的输出
/**************************************************************
Problem: 1179
User: mjy0724
Language: C++
Result: Accepted
Time:6804 ms
Memory:52260 kb
****************************************************************/
#include<cstdio>
#include<cstdlib>
#include<cstring>
const int maxn = ;
int cnt,time,top,n,m,s,p,e,fa[maxn],next[maxn],link[maxn],x[maxn],y[maxn],b[maxn];
int stack[maxn],low[maxn],dfn[maxn],pos[maxn],a[maxn],w[maxn],opt[maxn],f[maxn];
bool vis[maxn],ins[maxn],isp[maxn],isbar[maxn];
void add(int x,int y){
fa[++e]=y;next[e]=link[x];link[x]=e;
}
int min(int a,int b){
if (a<b) return a;
return b;
}
int max(int a,int b){
if (a>b) return a;
return b;
}
void tarjan(int p){
dfn[p]=low[p]=++time;stack[++top]=p;
vis[p]=false;ins[p]=true;
for (int i=link[p];i!=;i=next[i])
if (vis[fa[i]]){
tarjan(fa[i]);
low[p]=min(low[p],low[fa[i]]);
}else if (ins[fa[i]]) low[p]=min(low[p],dfn[fa[i]]);
if (low[p]==dfn[p]){
cnt++;
do{
pos[stack[top]]=cnt;
a[cnt]+=w[stack[top]];
isbar[cnt]=isbar[cnt] or isp[stack[top]];
ins[stack[top--]]=false;
}while (stack[top+]!=p);
}
}
void dfs(int p){
vis[p]=false;
for (int i=link[p];i;i=next[i]){
b[fa[i]]++;
if (vis[fa[i]]) dfs(fa[i]);
}
}
void solve(int s){
int head=,tail=;opt[]=s;f[s]=a[s];
while (head!=tail){
int x=f[opt[++head]];
for (int i=link[opt[head]];i;i=next[i]){
f[fa[i]]=max(f[fa[i]],x+a[fa[i]]);
b[fa[i]]--;
if (!b[fa[i]]) opt[++tail]=fa[i];
}
}
int ans=;
for (int i=;i<=cnt;i++) if (isbar[i]) ans=max(ans,f[i]);
printf("%d\n",ans);
}
int main(){
scanf("%d%d",&n,&m);
e=;
for (int i=;i<=m;i++){
scanf("%d%d",&x[i],&y[i]);
add(x[i],y[i]);
}
for (int i=;i<=n;i++) scanf("%d",&w[i]);
scanf("%d%d",&s,&p);
memset(isp,false,sizeof(isp));
for (int i=;i<=p;i++){
int tmp;
scanf("%d",&tmp);
isp[tmp]=true;
}
time=;top=;
memset(vis,true,sizeof(vis));
memset(ins,false,sizeof(ins));
memset(isbar,false,sizeof(isbar));
for (int i=;i<=n;i++) if (vis[i]) tarjan(i);
memset(link,,sizeof(link));
memset(next,,sizeof(next));
e=;
for (int i=;i<=m;i++) if (pos[x[i]]!=pos[y[i]]) add(pos[x[i]],pos[y[i]]);
memset(vis,true,sizeof(vis));
dfs(pos[s]);
solve(pos[s]);
return ;
}
最新文章
- bzoj1468 Tree
- 最简单的RASPBERRY PI wifi配置
- MVC区域 视图必须派生自 WebViewPage 或 WebViewPage<;TModel>;
- spring简单事务管理器
- asp.net 下载Excel (数据流,不保存)--客户端
- C# String.Format大全
- Dubbo扩展点加载机制
- Windows 下 Apache HTTP Server 安装、配置以及与 Tomcat 的整合(附图)
- XAF-BI.Dashboard模块概述 web/win
- dwr推送技术深入研究
- JS 清除DOM 中空白元素节点
- Linux samba多用户挂载
- stock1114
- JVM运行时数据区与JVM堆内存模型小结
- Linux sendmail
- visual stdio 工程 宏
- bzoj3675【APIO2014】序列切割
- bzoj2337 XOR和路径
- thinkphp5保存远程图片到本地
- [转载]关于python字典类型最疯狂的表达方式
热门文章
- BluetoothAdapter解析
- OSG学习:移动/缩放/旋转模型
- OSG学习:基本几何体绘制示例
- Android Studio类中实现Serializable自动生成serialVersionUID
- matlab中nargin函数的用法
- httpservlet在创建实例对象时候默认调用有参数的init方法 destroy()方法 service方法, 父类的init方法给子类实例一个config对象
- 前台界面(2)---CSS 样式
- git log 查看提交记录
- CentOS 磁盘阵列(raid10)
- axios请求,拦截器的使用