APIO2007-2015题解大集合(2007年篇)
2016-04-14 22:08
309 查看
系列题解总述
最近一周都在刷历年的APIO题,发现网上APIO的题解并不是很多,正好做了感觉这些题都挺有意思的,于是准备发一个APIO2007到2015年这9年题目的题解集合(好像还没人做过这件事情……)APIO给我的感觉是有很多题比较考思维,也有写起来需要很多细节的题。有意思的是,APIO并没有出现过中国特色数据结构(给你一个序列,支持XXXX操作,来个可持久化树套树看出题人心情带不带垃圾回收……),尽管2010年是中国出题;相反,APIO中很多题目是贪心和DP,这也是能够充分考验一个选手的分析能力的题目类型。另外:之后的题目中,如果出现了【上古预警】,则代表这些题目是作者很早的时候做的,记忆可能比较搓,方法比较笨,代码风格可能比较丑……
风铃【上古预警】
首先由于层数在交换过程中不能变化,显然如果一开始就存在相差超过2层的情况必然无解。然后考虑“合并”两个分支的情况。现在风铃有可能在上一层(我们定义为“高”),也可能在下一层(我们定义为“矮”)。我们认为一个子树具有三种状态:纯高,纯矮和 高矮兼有。令f[x]为x子树交换成满足条件的交换次数,则显然有f[x]=f[son1]+f[son2]+k,k视两边的情况而定。
下面是具体情况:(下面只介绍左右儿子均有的情况,只有一个儿子的情况留给读者自行思考)
1、左边复杂,右边纯高,k=0;
2、左边复杂,右边纯矮,k=1;
3、左右一样,k=0;
4、左边纯高,k=1;
5、右边纯矮,k=1;
然后就是注意特判和爆栈的问题了。作者写这个代码的时候还没学手工栈,所以用了一种讨巧的方法(详见代码)
#include<iostream> #include<iomanip> #include<algorithm> #include<cstring> #include<cstdio> #include<cstdlib> #include<cmath> using namespace std; //基本思路:判断无解:首先如果在图中一开始就有层数差距大于1,则不可能成功。 //否则,肯定是能成功的。 //这样我们可以对左右的“纯”和“不纯”进行讨论 int n,maxd=0,mind=999999999,deep[100005]={0},d[100005]={0},l[100005]={0},r[100005]={0}; int f[100005]={0}; bool high[100005]={0}; int pure[100005]={0}; void input() {scanf("%d",&n); for(int i=1;i<=n;i++) {scanf("%d%d",&l[i],&r[i]); } } void dfs(int x,int depth) {deep[x]=depth; //cout<<x<<' '<<depth<<endl; if(l[x]==-1||r[x]==-1) {maxd=max(maxd,depth+1),mind=min(mind,depth+1); if(maxd-mind>1){printf("-1");exit(0);}//基本避免了要写手工栈的情况(显然如果都在最下面一层那么层数是log级的绝对不会爆栈,如果不是那么必然中间会出现不满足的情况。现在只有类似哈夫曼树的感觉可以卡爆) } if(l[x]!=-1)dfs(l[x],depth+1); if(r[x]!=-1)dfs(r[x],depth+1); } void dfs2(int x) {high[x]=0,pure[x]=0; if(l[x]==-1||r[x]==-1) {//cout<<"!!!"<<x<<' '<<deep[x]<<" "<<mind<<endl; if(deep[x]==mind-1)high[x]=1; } if(l[x]==-1&&r[x]==-1) {if(deep[x]==mind-1)pure[x]=1;//pure=1是纯高,pure=2是纯矮,pure=0是不纯 else pure[x]=2; } if(l[x]!=-1)dfs2(l[x]); if(r[x]!=-1)dfs2(r[x]); if(l[x]!=-1)high[x]=high[x]||high[l[x]]; if(r[x]!=-1)high[x]=high[x]||high[r[x]]; if(l[x]!=-1&&r[x]==-1) {if(deep[x]==mind-1&&pure[l[x]]==1)pure[x]=1; else if(deep[x]==maxd-1&&pure[l[x]]==2)pure[x]=2; else pure[x]=0; } if(l[x]==-1&&r[x]!=-1) {if(deep[x]==mind-1&&pure[r[x]]==1)pure[x]=1; else if(deep[x]==maxd-1&&pure[r[x]]==2)pure[x]=2; else pure[x]=0; } if(l[x]!=-1&&r[x]!=-1) {if(pure[l[x]]==pure[r[x]])pure[x]=pure[l[x]]; else pure[x]=0;//否则不纯。左右都不纯的情况包含在了上一种里面。 } //cout<<"forth"<<x<<' '<<high[x]<<' '<<pure[x]<<endl; } void treedp(int x) {f[x]=0;//如果它两个都是风铃,那么理论上讲一开始就应该已经排好了 if(l[x]==-1&&r[x]==-1)return; if(l[x]!=-1)treedp(l[x]); if(r[x]!=-1)treedp(r[x]); //if(high[l[x]]&&high[r[x]])//如果两边都有高的,并且没有一个是纯的,那么是绝对不可能成功的! if(l[x]!=-1&&r[x]==-1) {if(deep[x]==maxd-1&&pure[l[x]]!=2)f[x]=f[l[x]]+1;//右矮 else f[x]=f[l[x]]; } if(l[x]==-1&&r[x]!=-1) {if(deep[x]==mind-1&&pure[r[x]]!=1)f[x]=f[r[x]]+1;//左高 else f[x]=f[r[x]]; } else if(l[x]!=-1&&r[x]!=-1) {if(pure[l[x]]==0&&pure[r[x]]==0){printf("-1");exit(0);} else if(pure[l[x]]==0&&pure[r[x]]==1)//如果左复杂,右纯高 f[x]=f[l[x]]+f[r[x]]; else if(pure[l[x]]==0&&pure[r[x]]==2)//左复杂,右纯矮 f[x]=f[l[x]]+f[r[x]]+1; else if(pure[l[x]]==pure[r[x]]);//左右一样,不需要交换。注意这就免去了后面左纯高/纯矮特判 else if(pure[l[x]]==1)//左纯高,需要额外交换一次 f[x]=f[l[x]]+f[r[x]]+1; else if(pure[l[x]]==2)//左纯矮,不需要额外交换 f[x]=f[l[x]]+f[r[x]]; } } int main() {input(); dfs(1,0); if(maxd-mind>1){printf("-1");return 0;} else if(maxd==mind){printf("0");return 0;} //cout<<maxd<<' '<<mind<<endl; dfs2(1);//处理看下面有没有“高的儿子” if(high[1]==0){printf("0");return 0;} treedp(1); printf("%d",f[1]); fclose(stdin); fclose(stdout); return 0; } /* 6 2 3 -1 4 5 6 -1 -1 -1 -1 -1 -1 */
数据备份【上古预警】
经典的堆+双向链表问题,NOIP级别的压轴题。首先30分以上都必须看出一个性质:配对不能相交。如果相交,一定可以通过重新配对使总长度更小。
于是把楼之间的空隙看成点,这个题就变成从n-1个点里选k个点,并且不能相邻,要求费用最小。
这是一个经典的堆+双向链表问题,每次选一个权值最小的点,把它的左右删除,用左+右-当前权值的点代替它,用堆进行优化。重复K次即答案。
#include<iostream> #include<iomanip> #include<algorithm> #include<cstring> #include<cstdio> #include<cstdlib> #include<cmath> using namespace std; long long ans=0,heapnum=0,n,k,a[100005]={0},pre[100005]={0},nxt[100005]={0}; long long eliminate[100005]={0}; long long s[100005]={0},hpos[100005]={0}; long long f[305][305]={0}; struct node{long long x,pos;}heap[500005]={0}; void heapup() {long long i=heapnum; while(i>1&&heap[i].x<heap[i/2].x)swap(hpos[heap[i].pos],hpos[heap[i/2].pos]),swap(heap[i],heap[i/2]),i/=2; } void setheap(long long x,long long pos) {heapnum++; heap[heapnum].x=x,heap[heapnum].pos=pos,hpos[pos]=heapnum; heapup(); } void heapdown() {long long i=1,j; while(i*2<=heapnum) {if(i*2==heapnum||heap[i*2].x<heap[i*2+1].x)j=i*2; else j=i*2+1; if(heap[i].x>heap[j].x)swap(hpos[heap[i].pos],hpos[heap[j].pos]),swap(heap[i],heap[j]),i=j; else break; } } void del() {heap[1]=heap[heapnum],hpos[heap[heapnum].pos]=1; heapnum--; heapdown(); } void input() {scanf("%lld%lld",&n,&k); for(long long i=1;i<=n;i++) scanf("%lld",&a[i]); n--; for(long long i=1;i<=n;i++) {s[i]=a[i+1]-a[i]; setheap(s[i],i); } for(long long i=1;i<=n;i++) pre[i]=i-1,nxt[i]=i+1; nxt =pre[1]=0; } void solve() {//heap[0].x=999999999999999LL; for(long long i=1;i<=k;i++)//一次次进行处理 {node x=heap[1];del(); while(eliminate[x.pos]||x.x!=s[x.pos])x=heap[1],del(); ans+=x.x; //heap[fa].eliminate=heap[son].eliminate=heap[1].eliminate=1; eliminate[x.pos]=eliminate[pre[x.pos]]=eliminate[nxt[x.pos]]=1; if(pre[x.pos]&&nxt[x.pos]) {eliminate[x.pos]=0; s[x.pos]=s[pre[x.pos]]+s[nxt[x.pos]]-s[x.pos]; pre[x.pos]=pre[pre[x.pos]],nxt[pre[x.pos]]=x.pos; nxt[x.pos]=nxt[nxt[x.pos]],pre[nxt[x.pos]]=x.pos; setheap(s[x.pos],x.pos); } else if(!pre[x.pos])pre[nxt[nxt[x.pos]]]=0; else if(!nxt[x.pos])nxt[pre[pre[x.pos]]]=0; } } int main() {input(); solve(); printf("%lld",ans); fclose(stdin); fclose(stdout); return 0; }
动物园
这个题容易使人想到网络流最小割的二选一问题……然而并不是。考虑每个人只能看到5个围栏,那么显然除了最靠近的5个围栏,前面有多少小朋友高兴与现在的选择是无关的(即最优子结构性质)。
于是不难想到状压DP:f[i,j]表示前i个围栏,最近5个围栏选与不选的二进制状态为j的情况。(最低位离得最近)
则f[i,j]=max(f[i,(j<<1)&31]+k1,f[i,(j<<1)&31+1]+k2)。前者代表撤走当前动物,后者代表保留当前动物,k1和k2都可以通过预处理求出。预处理时可以对每个小朋友看到的5个围栏的状态进行暴力枚举来确定每种状态下这一段有多少小朋友高兴。
另外此题的围栏构成了一个环,故需要枚举最开始的4个围栏。此题细节繁多,注意各种判断!
<span style="font-weight: normal;">#include<iostream>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;
int cnt=0,ans=0,n,c,a,b,d,f[50005][33]={0},num[50005][33]={0},h[50005]={0};
struct node{int st,love,hate;}child[50005];
struct nodf{int next,to;}edge[50005];
void Addedge(int x,int y)
{cnt++,edge[cnt].to=y,edge[cnt].next=h[x],h[x]=cnt;
}
void Dfs(int x,int status,int depth)
{if(depth>=5)
{if(((child[x].hate&status)!=child[x].hate)||(child[x].love&status))num[child[x].st][status]++;
return;
}
Dfs(x,status,depth+1);
Dfs(x,status|(1<<depth),depth+1);
}
void Input()
{scanf("%d%d",&n,&c);
for(int i=1;i<=c;i++)
{scanf("%d%d%d",&child[i].st,&a,&b);
child[i].love=child[i].hate=0;
for(int j=1;j<=a;j++)
{scanf("%d",&d);
child[i].hate|=(1<<((d-child[i].st+n)%n));
}
for(int j=1;j<=b;j++)
{scanf("%d",&d);
child[i].love|=(1<<((d-child[i].st+n)%n));
}
Dfs(i,0,0);
}
}
void DP(int state)
{for(int i=1;i<=n;i++)
{if(i<5)
{for(int j=0;j<=31;j++)
{int q=(state>>(i-1)),r=(1<<(5-i))-1;
if((j&r)==q)
{f[i][j]=max(f[i-1][(j<<1)&31],f[i-1][((j<<1)&31)+1])+num[i][j];
}
else f[i][j]=0;
}
}
else if(i+3<n)
{for(int j=0;j<=31;j++)
f[i][j]=max(f[i-1][(j<<1)&31],f[i-1][((j<<1)&31)+1])+num[i][j];
}
else
{int q=state&((1<<(i+4-n))-1);
for(int j=0;j<=31;j++)
{if((j>>(n-i+1))==q)
{f[i][j]=max(f[i-1][(j<<1)&31],f[i-1][((j<<1)&31)+1])+num[i][j];
}
else f[i][j]=0;
}
}
}
for(int i=0;i<=31;i++)
{ans=max(ans,f
[i]);
}
}
void Solve()
{for(int i=0;i<=1;i++)
for(int j=0;j<=1;j++)
for(int k=0;k<=1;k++)
for(int l=0;l<=1;l++)
{for(int q=0;q<=31;q++)
f[0][q]=0;
DP(i*8+j*4+k*2+l);
}
printf("%d\n",ans);
}
int main()
{Input();
Solve();
return 0;
}</span>
相关文章推荐
- [leetcode 240/74] Search a 2D Matrix II -----在左右有序,上下有序中查找数据
- HDU 3790最短路径问题 dijikstra算法
- jquery之ajaxfileupload异步上传插件
- 基于MVC项目iOS项目重构的思路
- 复利计算
- OneZero第四周第四次站立会议(2016.4.14)
- 稀疏矩阵的加法(用十字链表实现A=A+B)
- [CodeForces 573B] Bear and Blocks (脑洞题)
- Oracle-26-内连接(等值、不等值连接、自然连接)&外连接(左外、右外、全连接)&using子句
- 微软等面试100题系列 - 45(3)
- java之迭代对象Iterator的使用方法
- 20145313张雪纯 《Java程序设计》第7周学习总结
- C++编程基础笔记章节二
- Java BIO、NIO、AIO 学习
- hdu5001(概率dp)
- Java邮件开发-----JavaMail(三)
- hdu 2181.。。。
- 浅谈复杂链表的复制
- static final abstract
- 基于 HTML5 的人脸识别技术