HEOI2016解题报告
Posted zh-comld
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HEOI2016解题报告相关的知识,希望对你有一定的参考价值。
树
在2016年,佳媛姐姐刚刚学习了树,非常开心。现在他想解决这样一个问题:给定一颗有根树(根为1),有以下
两种操作:1. 标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均无标记,而且对于某个
结点,可以打多次标记。)2. 询问操作:询问某个结点最近的一个打了标记的祖先(这个结点本身也算自己的祖
先)你能帮帮他吗?
题解
这题如果直接做的话,就是一个裸的树链剖分。
但也有一个更加巧妙的做法,如果把操作序列倒过来看,打标记就变成了删标记。
如果用一个数组来维护答案的话,这个操作相当于把指向这个点的位置的答案改成这个点的答案,可以直接用并查集来维护这一过程。
#include<iostream> #include<cstdio> #define N 100009 using namespace std; int f[N],head[N],tot,n,q,a[N],fa[N],ans[N],top,tag[N]; char c[N]; struct dwd { int n,to; } e[N<<1]; inline void add(int u,int v) { e[++tot].n=head[u]; e[tot].to=v; head[u]=tot; } int find(int x) { return f[x]=f[x]==x?x:find(f[x]); } inline char getch() { char c=getchar(); while(!isalpha(c))c=getchar(); return c; } int rd() { int x=0; char c=getchar(); while(!isdigit(c))c=getchar(); while(isdigit(c)) { x=(x<<1)+(x<<3)+(c^48); c=getchar(); } return x; } void dfs(int u,int faa) { for(int i=head[u]; i; i=e[i].n) { int v=e[i].to; if(v==faa)continue; fa[v]=u; if(!tag[v])f[v]=u; else f[v]=v; dfs(v,u); } } int main() { n=rd(); q=rd(); int u,v; for(int i=1; i<n; ++i)u=rd(),v=rd(),add(u,v),add(v,u); tag[1]=1; f[1]=1; for(int i=1; i<=q; ++i) { c[i]=getch(); a[i]=rd(); if(c[i]==‘C‘)tag[a[i]]++; } dfs(1,0); for(int i=q; i>=1; --i) { if(c[i]==‘C‘) { tag[a[i]]--; if(!tag[a[i]])f[a[i]]=fa[a[i]]; } else ans[++top]=find(a[i]); } for(int i=top; i>=1; --i)printf("%d ",ans[i]); return 0; }
排序
在2016年,佳媛姐姐喜欢上了数字序列。因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题
,需要你来帮助他。这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排
序分为两种:1:(0,l,r)表示将区间[l,r]的数字升序排序2:(1,l,r)表示将区间[l,r]的数字降序排序最后询问第q
位置上的数字。
题解
答案具有单调性,可以直接二分答案。
然后把大于这个数的标为1,小的标成0,然后那些操作直接一通做下来就好了。
只有01两种数,线段树直接统计就可以。
#include<iostream> #include<cstdio> #define N 30002 using namespace std; int la[N<<2],tr[N<<2],a[N],ji,tag,n,m,l[N],r[N],ta[N],ans,q; bool b[N]; inline void pushdown(int cnt,int l1,int l2){ int sum=tr[cnt]; if(la[cnt]==1){ if(l1>=sum){ tr[cnt<<1]=sum;sum=0; } else{ tr[cnt<<1]=l1;sum-=l1; } tr[cnt<<1|1]=sum; } else{ if(l2>=sum){ tr[cnt<<1|1]=sum;sum=0; } else{ tr[cnt<<1|1]=l2;sum-=l2; } tr[cnt<<1]=sum; } la[cnt<<1]=la[cnt<<1|1]=la[cnt];la[cnt]=0; } int dfs(int cnt,int l,int r){ if(l==r)return tr[cnt]; int mid=(l+r)>>1; if(la[cnt])pushdown(cnt,mid-l+1,r-mid); if(mid>=q)dfs(cnt<<1,l,mid); else dfs(cnt<<1|1,mid+1,r); } void build(int cnt,int l,int r){ if(l==r){ tr[cnt]=b[l]; return; } int mid=(l+r)>>1; build(cnt<<1,l,mid);build(cnt<<1|1,mid+1,r); tr[cnt]=tr[cnt<<1]+tr[cnt<<1|1];//care } void gett(int cnt,int l,int r,int L,int R){ if(l>=L&&r<=R){ ji+=tr[cnt]; return; } int mid=(l+r)>>1; if(la[cnt])pushdown(cnt,mid-l+1,r-mid); if(mid>=L)gett(cnt<<1,l,mid,L,R); if(mid<R)gett(cnt<<1|1,mid+1,r,L,R); } void returnn(int cnt,int l,int r,int L,int R){ if(l>=L&&r<=R){ la[cnt]=tag; if(ji>=r-l+1){ tr[cnt]=r-l+1; ji-=tr[cnt]; } else{ tr[cnt]=ji;ji=0; } return; } int mid=(l+r)>>1; if(tag==1){ if(mid>=L)returnn(cnt<<1,l,mid,L,R); if(mid<R)returnn(cnt<<1|1,mid+1,r,L,R); } else{ if(mid<R)returnn(cnt<<1|1,mid+1,r,L,R); if(mid>=L)returnn(cnt<<1,l,mid,L,R); } tr[cnt]=tr[cnt<<1]+tr[cnt<<1|1]; } bool ch(int pos){ for(int i=1;i<=n;++i)b[i]=a[i]>=pos?1:0; build(1,1,n); for(int i=1;i<=m;++i){ ji=0; tag=ta[i]; gett(1,1,n,l[i],r[i]); returnn(1,1,n,l[i],r[i]); } if(dfs(1,1,n))return 1; else return 0; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) scanf("%d",&a[i]); for(int i=1;i<=m;++i){scanf("%d%d%d",&ta[i],&l[i],&r[i]);if(!ta[i])ta[i]=2;} scanf("%d",&q); int l=1,r=n; while(l<=r){ int mid=(l+r)>>1; if(ch(mid)){ ans=mid; l=mid+1; } else r=mid-1; } cout<<ans; return 0; }
序列
佳媛姐姐过生日的时候,她的小伙伴从某宝上买了一个有趣的玩具送给他。玩具上有一个数列,数列中某些项的值
可能会变化,但同一个时刻最多只有一个值发生变化。现在佳媛姐姐已经研究出了所有变化的可能性,她想请教你
,能否选出一个子序列,使得在任意一种变化中,这个子序列都是不降的?请你告诉她这个子序列的最长长度即可
。注意:每种变化最多只有一个值发生变化。在样例输入1中,所有的变化是:
1 2 3
2 2 3
1 3 3
1 1 31 2 4
选择子序列为原序列,即在任意一种变化中均为不降子序列在样例输入2中,所有的变化是:3 3 33 2 3选择子序列
为第一个元素和第三个元素,或者第二个元素和第三个元素,均可满足要求
游戏
在2016年,佳缘姐姐喜欢上了一款游戏,叫做泡泡堂。简单的说,这个游戏就是在一张地图上放上若干个炸弹,看
是否能炸到对手,或者躲开对手的炸弹。在玩游戏的过程中,小H想到了这样一个问题:当给定一张地图,在这张
地图上最多能放上多少个炸弹能使得任意两个炸弹之间不会互相炸到。炸弹能炸到的范围是该炸弹所在的一行和一
列,炸弹的威力可以穿透软石头,但是不能穿透硬石头。给定一张n*m的网格地图:其中*代表空地,炸弹的威力可
以穿透,可以在空地上放置一枚炸弹。x代表软石头,炸弹的威力可以穿透,不能在此放置炸弹。#代表硬石头,炸
弹的威力是不能穿透的,不能在此放置炸弹。例如:给出1*4的网格地图*xx*,这个地图上最多只能放置一个炸弹
。给出另一个1*4的网格地图*x#*,这个地图最多能放置两个炸弹。现在小H任意给出一张n*m的网格地图,问你最
多能放置多少炸弹
题解
考虑没有墙的情况,把每行每列看做点,如果哪个位置有炸弹,就在行和列之间连边,跑二分图匹配。
如果有墙,就在墙的右边和下面为这一行和这一列新开点就可以了。
这一年的题好水
以上是关于HEOI2016解题报告的主要内容,如果未能解决你的问题,请参考以下文章