您的位置:首页 > 其它

APIO2007-2015题解大集合(2009年篇)

2016-04-14 23:23 246 查看

采油区域【上古预警】

这个题我半年前拿到是一脸懵逼的,当时调了一中午才调出来……此题简直跟NOIP2002矩形覆盖是一个感觉,都具有超级复杂的讨论……
这种题一个非常重要的辅助手段就是前缀和——对于矩形问题,是二维前缀和(虽然说相信NOIP提高组能拿300分以上的同学都知道二维前缀和是什么……);但是此题要预处理4个方向的前缀和,这确实不太多见。总之先预处理出4个角的二维前缀和(左上,左下,右上,右下),然后考虑这3个矩形的摆放情况。这3个矩形有6种摆放方式,分别是:円,円翻转90度,円翻转180度,円翻转270度,四和目。(中华文化博大精深,用汉字就可以省去画图的麻烦……不过看不懂的同学还是照着汉字画个图更好)

对于前4种情况,枚举两维上的分割线处理即可;对于第5、6种情况,则需要预处理出行与列夹住的中间区域的最大值。

#include<iostream>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;
long long n,m,k,ans=0,map[1505][1505]={0},he[1505][1505]={0};
long long leftup[1505][1505]={0},leftdown[1505][1505]={0},rightup[1505][1505]={0},rightdown[1505][1505]={0};
long long hang[1505]={0},lie[1505]={0},row[1505][1505]={0},column[1505][1505]={0};
void input()
{scanf("%lld%lld%lld",&n,&m,&k);
for(long long i=1;i<=n;i++)
for(long long j=1;j<=m;j++)
scanf("%lld",&map[i][j]);
for(long long i=1;i<=n;i++)
for(long long j=1;j<=m;j++)
he[i][j]=map[i][j]+he[i][j-1]+he[i-1][j]-he[i-1][j-1];

}
void prepare()
{for(long long i=1;i<=n;i++)
for(long long j=1;j<=m;j++)
{leftup[i][j]=max(leftup[i][j-1],leftup[i-1][j]);
if(i>=k&&j>=k)leftup[i][j]=max(leftup[i][j],he[i][j]-he[i-k][j]-he[i][j-k]+he[i-k][j-k]);
}
for(long long i=1;i<=n;i++)
for(long long j=m;j>=1;j--)
{rightup[i][j]=max(rightup[i][j+1],rightup[i-1][j]);
if(i>=k&&j+k-1<=m)rightup[i][j]=max(rightup[i][j],he[i][j+k-1]-he[i-k][j+k-1]-he[i][j-1]+he[i-k][j-1]);
}
for(long long i=n;i>=1;i--)
for(long long j=1;j<=m;j++)
{leftdown[i][j]=max(leftdown[i][j-1],leftdown[i+1][j]);
if(i+k-1<=m&&j>=k)leftdown[i][j]=max(leftdown[i][j],he[i+k-1][j]-he[i+k-1][j-k]-he[i-1][j]+he[i-1][j-k]);
}
for(long long i=n;i>=1;i--)
for(long long j=m;j>=1;j--)
{rightdown[i][j]=max(rightdown[i][j+1],rightdown[i-1][j]);
if(i+k-1<=m&&j+k-1<=m)rightdown[i][j]=max(rightdown[i][j],he[i+k-1][j+k-1]-he[i-1][j+k-1]-he[i+k-1][j-1]+he[i-1][j-1]);
}
for(long long i=1;i<=n;i++)
for(long long j=1;j<=m;j++)
if(i>=k&&j>=k)
{hang[i]=max(hang[i],he[i][j]-he[i-k][j]-he[i][j-k]+he[i-k][j-k]);
lie[j]=max(lie[j],he[i][j]-he[i-k][j]-he[i][j-k]+he[i-k][j-k]);
}
for(long long i=1;i<=n;i++)
for(long long j=1;j+k-1<=i;j++)
{row[j][i]=hang[i];
if(j>1)row[j][i]=max(row[j][i],row[j-1][i]);
}
for(long long i=1;i<=m;i++)
for(long long j=1;j+k-1<=i;j++)
{column[j][i]=lie[i];
if(j>1)column[j][i]=max(column[j][i],column[j-1][i]);
}
for(long long i=1;i<=n;i++)//第五种情况:目
for(long long j=1;j<i;j++)
{if(i<k||j<k||i+k>n||i-j+1<k)continue;
ans=max(ans,leftup[j][m]+leftdown[i+1][m]+row[j+1][i]);
}
for(long long i=1;i<=m;i++)//第六种情况:四
for(long long j=1;j<i;j++)
{if(i<k||j<k||i+k>m||i-j+1<k)continue;
ans=max(ans,leftup
[j]+rightup
[i+1]+column[j+1][i]);
}
}
void solve1()//先处理4种情况
{for(long long i=1;i<=n;i++)
for(long long j=1;j<=m;j++)
{if(i>=k&&j>=k&&j+k<=m&&i+k<=m)
{ans=max(ans,leftup[i][j]+rightup[i][j+1]+leftdown[i+1][m]);
ans=max(ans,leftup
[j]+rightup[i][j+1]+rightdown[i+1][j+1]);
ans=max(ans,leftup[i][m]+leftdown[i+1][j]+rightdown[i+1][j+1]);
ans=max(ans,leftup[i][j]+rightup
[j+1]+leftdown[i+1][j]);
}
}
}
int main()
{input();
prepare();
solve1();
printf("%lld",ans);
return 0;
}


会议中心

首先如果不考虑输出字典序最小解,则此题即普及组贪心水题。现在有字典序问题,我们考虑在求出答案后枚举加入边。(这是一种常用思想,类似的思想见

于求字典序最小的最小割。)

不难想到,在最优方案下,每条线段接下来该取哪条是确定的。这时我们为了确定整个序列,就考虑倍增。(类似的思想见于NOIP2012开车旅行。)于是我

们设f[i,j]表示第i条线段向后取2^j条线段的最小右端点,这可以通过倍增求出。于是我们有了快速求出一个给定坐标范围内答案的方法:从左端点开始,从长到短

试着跳,如果能跳,则跳到该右端点+1除处,同时把对应跳过的2^j条线段累加入答案。现在我们从小到大枚举(这样能够保证之前放下的线段优先级必然高于

之后),用一个set维护当前解集:首先在set中二分找出离当前考虑的线段端点最接近的两个区间,若它们之间直接的答案等于被中间隔断的两端区间答案之和

+1,则当前区间合法,放入set,最后输出set中元素即可。另外此题不要去除包含的区间——有可能会漏掉字典序更小解。


<span style="font-size:12px;font-weight: normal;">#include<iostream>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<set>
using namespace std;
int n,tot,stage,top,lisan[400005],nxt[400005][22]={0},c[400005]={0};
struct node{int st,ed,id;}line[400005],stk[400005];
set<node>s;
bool operator<(node x,node y)
{return x.st<y.st||(x.st==y.st&&x.ed<y.ed);
}
bool Comp2(node x,node y)
{return x.id<y.id;
}
int Lowbit(int i){return i&(-i);}
void Add(int x,int v)
{for(int i=x;i<=tot;i+=Lowbit(i))c[i]+=v;
}
int Sum(int x)
{int ret=0;
for(int i=x;i>=1;i-=Lowbit(i))
ret+=c[i];
return ret;
}
void Input()
{lisan[0]=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{scanf("%d%d",&line[i].st,&line[i].ed);
lisan[++lisan[0]]=line[i].st,lisan[++lisan[0]]=line[i].ed;
line[i].id=i;
}
sort(lisan+1,lisan+lisan[0]+1);
tot=unique(lisan+1,lisan+lisan[0]+1)-lisan-1,top=0;
stage=int(log(tot)/log(2))+1;
sort(line+1,line+n+1);
for(int i=1;i<=n;i++)
{line[i].st=lower_bound(lisan+1,lisan+tot+1,line[i].st)-lisan;
line[i].ed=lower_bound(lisan+1,lisan+tot+1,line[i].ed)-lisan;
}
}
void St()
{for(int i=1;i<=tot+1;i++)nxt[i][0]=999999999;
for(int i=1;i<=n;i++)nxt[line[i].st][0]=line[i].ed;//这里好像有些不对。。。
for(int i=tot-1;i>=1;i--)nxt[i][0]=min(nxt[i+1][0],nxt[i][0]);
for(int j=1;j<=stage;j++)
{nxt[tot+1][j]=999999999;
for(int i=1;i<=tot;i++)
{if(nxt[i][j-1]!=999999999)
nxt[i][j]=nxt[nxt[i][j-1]+1][j-1];
else nxt[i][j]=999999999;
}
}
}
int Ans(int l,int r)
{int ret=0,now=l;
if(l>r)return 0;
for(int j=stage;j>=0;j--)
{if(nxt[now][j]<=r)ret+=(1<<j),now=nxt[now][j]+1;
}
return ret;
}
void Solve()
{int wer=Ans(1,tot);
sort(line+1,line+n+1,Comp2);
printf("%d\n",wer);
for(int i=1;i<=n;i++)
{int L,R,ans1,ans2;
if(Sum(line[i].ed)-Sum(line[i].st-1)>0)continue;
set<node>::iterator it=s.upper_bound(line[i]);
if(it==s.end()) R=tot;
else R=(*it).st-1;
ans2=Ans(line[i].ed+1,R);
if(it==s.begin()) L=1;
else {it--;L=(*it).ed+1;}
ans1=Ans(L,line[i].st-1);
if(ans1+ans2+1==Ans(L,R))
{printf("%d ",i);
s.insert(line[i]);
for(int j=line[i].st;j<=line[i].ed;j++)Add(j,1);
}
}
}
int main()
{Input();
St();
Solve();
return 0;
}
/*
5
9 17
20 30
10 15
1 9
10 16
*/</span>






抢掠计划【上古预警】

NOIP标准图论题,先进行强连通分量缩点,然后把每个SCC看成一个点重新建图,边权为边指向的联通块拥有的现金和,最后用spfa或者拓扑序递推求一次从

开始点到结束点最长路,对酒吧们取max就好。此题唯一的坑点在于:需要手工栈。

#include<iostream>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;
//基本思路:先kosaraju然后从1所在强连通分量出发找通往每个酒吧的最长路最后一扫便知。
long long BCC=0,cnt=0,ans=0,top=0,st,p,fcnt=0,newcnt=0,jishu=0,n,m,a,b,d[500005]={0},h[500005]={0},fh[500005]={0};
long long vis[500005]={0},money[500005]={0},newh[500005]={0},queue[500005]={0},dist[500005]={0},inqueue[500005]={0};
bool bar[500005]={0};
struct node{long long next,to;}edge[1000005],fedge[1000005];
struct nodf{long long next,to,v;}newedge[1000005];
struct nodg{long long x,i;}stk[600005];
long long getint()
{long long ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while('0'<=ch&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
void addedge(long long x,long long y)
{cnt++,edge[cnt].to=y,edge[cnt].next=h[x],h[x]=cnt;
fcnt++,fedge[fcnt].to=x,fedge[fcnt].next=fh[y],fh[y]=fcnt;
}
void addnewedge(long long x,long long y,long long z)
{newcnt++,newedge[newcnt].v=z,newedge[newcnt].to=y,newedge[newcnt].next=newh[x],newh[x]=newcnt;
}
void input()
{n=getint(),m=getint();
for(long long i=1;i<=m;i++)
{a=getint(),b=getint();
addedge(a,b);
}
}
void dfs1(long long x)
{vis[x]=1;
for(long long i=h[x];i;i=edge[i].next)
{long long y=edge[i].to;
if(!vis[y])dfs1(y);
}
d[++jishu]=x;
}
void dfs1_manual(long long x)
{int i,y;top=0;
stk[++top].x=x,stk[top].i=h[x];
START:;
x=stk[top].x,i=stk[top].i;
vis[x]=1;
for(i=h[x];i;i=edge[i].next)
{y=edge[i].to;
if(!vis[y])
{stk[top].i=i,stk[++top].x=y;
goto START;
}
MIDDLE:;
}
d[++jishu]=x;
if(top>1)
{top--;
i=stk[top].i,x=stk[top].x;
goto MIDDLE;
}
}
void dfs2(long long x)
{vis[x]=BCC;
for(long long i=fh[x];i;i=fedge[i].next)
{long long y=fedge[i].to;
if(!vis[y])dfs2(y);
}
}
void dfs2_manual(long long x)
{int i,y;top=0;
stk[++top].x=x,stk[top].i=fh[x];
ANOTHERSTART:;
x=stk[top].x,i=stk[top].i;
vis[x]=BCC;
for(i=fh[x];i;i=fedge[i].next)
{y=fedge[i].to;
if(!vis[y])
{stk[top].i=i,stk[++top].x=y;
goto ANOTHERSTART;
}
ANOTHERMIDDLE:;
}
if(top>1)
{top--;
i=stk[top].i,x=stk[top].x;
goto ANOTHERMIDDLE;
}
}
void kosaraju()
{
if(n<32767)
{for(long long i=1;i<=n;i++)
if(!vis[i])dfs1(i);
memset(vis,0,sizeof(vis));
for(long long i=n;i>=1;i--)
if(!vis[d[i]])BCC++,dfs2(d[i]);
}
else
{for(long long i=1;i<=n;i++)
if(!vis[i])dfs1_manual(i);
memset(vis,0,sizeof(vis));
for(long long i=n;i>=1;i--)
if(!vis[d[i]])BCC++,dfs2_manual(d[i]);
}
}
void buildgraph()
{for(long long i=1;i<=n;i++)
{a=getint();
money[vis[i]]+=a;
}
for(long long i=1;i<=n;i++)
{for(long long j=h[i];j;j=edge[j].next)
{long long y=edge[j].to;
if(vis[y]!=vis[i])addnewedge(vis[i],vis[y],money[vis[y]]);//现在是DAG了,不用担心正环
}
}
st=getint(),p=getint();
st=vis[st];
for(long long i=1;i<=p;i++)
{a=getint();
bar[vis[a]]=1;
}
}
void spfa()//注意是最长路!
{long long head=0,tail=1;
for(long long i=1;i<=BCC;i++)dist[i]=-999999999;
queue[tail]=st,inqueue[st]=1,dist[st]=money[st];//注意不是0!
while(head!=tail)
{head=(head+1)%500003;
long long x=queue[head];
inqueue[x]=0;
for(long long i=newh[x];i;i=newedge[i].next)
{long long y=newedge[i].to;
if(dist[x]+newedge[i].v>dist[y])
{dist[y]=dist[x]+newedge[i].v;
if(!inqueue[y])
{tail=(tail+1)%500003;
queue[tail]=y;
inqueue[y]=1;
}
}
}
}
}
void solve()
{for(long long i=1;i<=BCC;i++)
if(bar[i])ans=max(ans,dist[i]);
printf("%lld\n",ans);
}
int main()
{
input();
kosaraju();
buildgraph();
spfa();
solve();
fclose(stdin);
fclose(stdout);
return 0;
}
/*
6 7
1 2
2 3
1 6
6 3
3 5
5 6
2 4
25 30 17 75 21 33
4 2
4 6
*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: