HEOI2018题解
Posted moyiii
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HEOI2018题解相关的知识,希望对你有一定的参考价值。
得知了一切真相,弄清了一切是怎么发生的之后,可能没那么伤心了(假的,我更伤心了QAQ)。不能否认是一套非常棒的题目吧,应该是近几届质量最高的HEOI了,将来或许也很难超越。
Day1
T1《一双木棋》
得分:50
估分与得分一致,一致地低。没有梦想?没想过在省选A题,尽管HEOI2017的Day1T1都差点过了……也是因为一开始不知道怎么搜,后来会搜了之后就觉得可以了,没去尝试进一步得分。思路上主要是没有想到可以反向处理,显然正着做是只能枚举没有什么优化余地的。看上去很高端但是和博弈论并没有什么关系呢。
可以发现任何时候剩下的部分一定是一个右下角,用一个轮廓线表示当前状态(1是向上,0是向右),可以用数组记录从这往后的最优解。在转移的不同步数需要取min或max,而哪个地方可以转移是很显然的。相当好写,跑得飞快。好像同学们直接反向暴搜hash/map记忆化一下就过了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define inf 0x7fffffff 6 using namespace std; 7 const int sj=15; 8 int n,m,tot,a[sj][sj],b[sj][sj]; 9 bool v[sj][sj],h[sj][sj]; 10 inline int maxx(int x,int y) 11 { 12 return x>y?x:y; 13 } 14 inline int minn(int x,int y) 15 { 16 return x<y?x:y; 17 } 18 int dfs(int op,int sum) 19 { 20 int ret=0; 21 if(sum==tot) 22 { 23 for(int i=1;i<=n;i++) 24 for(int j=1;j<=m;j++) 25 { 26 if(h[i][j]) ret+=a[i][j]; 27 else ret-=b[i][j]; 28 } 29 return ret; 30 } 31 if(op==1) 32 { 33 ret=-inf; 34 for(int i=1;i<=n;i++) 35 for(int j=1;j<=m;j++) 36 if(!v[i][j]&&(i==1||v[i-1][j])&&(j==1||v[i][j-1])) 37 { 38 v[i][j]=h[i][j]=1; 39 ret=maxx(ret,dfs(0,sum+1)); 40 v[i][j]=h[i][j]=0; 41 } 42 } 43 else 44 { 45 ret=inf; 46 for(int i=1;i<=n;i++) 47 for(int j=1;j<=m;j++) 48 if(!v[i][j]&&(i==1||v[i-1][j])&&(j==1||v[i][j-1])) 49 { 50 v[i][j]=1,h[i][j]=0; 51 ret=minn(ret,dfs(1,sum+1)); 52 v[i][j]=h[i][j]=0; 53 } 54 } 55 return ret; 56 } 57 int main() 58 { 59 //freopen("chess1.in","r",stdin); 60 freopen("chess.in","r",stdin); 61 freopen("chess.out","w",stdout); 62 scanf("%d%d",&n,&m); 63 tot=n*m; 64 for(int i=1;i<=n;i++) 65 for(int j=1;j<=m;j++) 66 scanf("%d",&a[i][j]); 67 for(int i=1;i<=n;i++) 68 for(int j=1;j<=m;j++) 69 scanf("%d",&b[i][j]); 70 printf("%d",dfs(1,0)); 71 fclose(stdin);fclose(stdout); 72 return 0; 73 }
1 #include<iostream> 2 #include<cstdio> 3 #define inf 0x7fffffff 4 using namespace std; 5 const int sj=12; 6 int n,m,a[sj][sj],b[sj][sj],bin[sj<<1],s,f[1<<21]; 7 bool vi[1<<21]; 8 inline int maxx(int x,int y) 9 { 10 return x>y?x:y; 11 } 12 inline int minn(int x,int y) 13 { 14 return x<y?x:y; 15 } 16 int dfs(int zt,int op) 17 { 18 if(vi[zt]) return f[zt]; 19 vi[zt]=1; 20 if(op) f[zt]=inf; 21 else f[zt]=-inf; 22 int a1=0,a2=0; 23 if(zt&bin[0]) a1++; 24 else a2++; 25 for(int i=0;i<n+m-1;i++) 26 { 27 if(zt&bin[i+1]) a1++; 28 else a2++; 29 if((zt&bin[i])&&!(zt&bin[i+1])) 30 { 31 s=zt-bin[i]+bin[i+1]; 32 if(op) f[zt]=minn(f[zt],dfs(s,op^1)-b[n-a1+1][a2]); 33 else f[zt]=maxx(f[zt],dfs(s,op^1)+a[n-a1+1][a2]); 34 } 35 } 36 return f[zt]; 37 } 38 int main() 39 { 40 scanf("%d%d",&n,&m); 41 for(int i=1;i<=n;i++) 42 for(int j=1;j<=m;j++) 43 scanf("%d",&a[i][j]); 44 for(int i=1;i<=n;i++) 45 for(int j=1;j<=m;j++) 46 scanf("%d",&b[i][j]); 47 bin[0]=1; 48 for(int i=1;i<=n+m;i++) bin[i]=bin[i-1]<<1; 49 for(int i=1;i<=n;i++) s+=bin[i-1+m]; 50 vi[s]=1,s=0; 51 for(int i=1;i<=n;i++) s+=bin[i-1]; 52 printf("%d",dfs(s,0)); 53 return 0; 54 }
T2《iiidx》
得分:60
Day1主要的时间都给了这道题……得分不是很低,但实际上是大众分,并且剩下两题得分都不高。贪心的思路比较显然,在有相同权值时的锅也比较显然。因为我前面是直接把区间向下分治的,所以总是想着通过调整序列什么的改正这个做法,想了很久也没有进展。实际上我们并不能在父亲处就确定子树控制的区间(会出现我所担心的“兄弟并没有变大,儿子却变小了”的情况),所以正解的方向是数据结构维护。
为了字典序最大要逐位确定,先把序列降序排列。因为要保证子树里有足够的点,可以在处理每个点时把子树的大小预订出来。每个点维护左侧还有多少可行点,预订时就从当前点往右把预订值都减去,用线段树区间减实现。对于每个点,可行的位置需要满足右边所有点的权值都不小于它的size,在这些位置中选值最大的。所以线段树维护的是区间最小值,查询后在同样的数里选最靠右的作为更新位置。处理每个点子树中第一个点时应该把它的预订值消掉,不过父亲节点被选走造成的影响是不能消的。官方题解给出的例子非常清楚。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 using namespace std; 7 const int sj=500010; 8 int n,d[sj],h[sj],e,size[sj],fa,ans[sj]; 9 double k; 10 struct B 11 { 12 int ne,v; 13 }b[sj]; 14 void add(int x,int y) 15 { 16 b[e].v=y,b[e].ne=h[x],h[x]=e++; 17 } 18 inline int read() 19 { 20 int jg=0,jk=getchar()-\'0\'; 21 while(jk<0||jk>9) jk=getchar()-\'0\'; 22 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-\'0\'; 23 return jg; 24 } 25 void dfs(int x) 26 { 27 size[x]=1; 28 for(int i=h[x];i!=-1;i=b[i].ne) 29 dfs(b[i].v),size[x]+=size[b[i].v]; 30 } 31 void solve(int x,int l,int r) 32 { 33 //cout<<x<<" "<<l<<" "<<r<<endl; 34 ans[x]=d[r]; 35 if(l==r) return; 36 int pr=0; 37 for(int i=h[x];i!=-1;i=b[i].ne) 38 { 39 solve(b[i].v,l+pr,l+pr+size[b[i].v]-1); 40 pr+=size[b[i].v]; 41 } 42 } 43 int main() 44 { 45 //freopen("iiidx1.in","r",stdin); 46 //freopen("me.out","w",stdout); 47 freopen("iiidx.in","r",stdin); 48 freopen("iiidx.out","w",stdout); 49 scanf("%d%lf",&n,&k); 50 for(int i=1;i<=n;i++) scanf("%d",&d[i]); 51 sort(d+0,d+n+1,greater<int>()); 52 memset(h,-1,sizeof(h)); 53 for(int i=n;i>=1;i--) 54 { 55 fa=floor(i/k); 56 add(fa,i); 57 //cout<<i<<" "<<fa<<endl; 58 } 59 dfs(0); 60 solve(0,0,n); 61 for(int i=1;i<n;i++) printf("%d ",ans[i]); 62 printf("%d",ans[n]); 63 fclose(stdin);fclose(stdout); 64 return 0; 65 }
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #define inf 0x7fffffff 7 using namespace std; 8 const int sj=500010; 9 int n,d[sj],h[sj],e,size[sj],fa[sj],ans[sj],bd[sj]; 10 bool vi[sj]; 11 double k; 12 struct B 13 { 14 int ne,v; 15 }b[sj]; 16 struct tree 17 { 18 int l,r,v,lz; 19 }t[sj<<2]; 20 inline int read() 21 { 22 int jg=0,jk=getchar()-\'0\'; 23 while(jk<0||jk>9) jk=getchar()-\'0\'; 24 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-\'0\'; 25 return jg; 26 } 27 void dfs(int x) 28 { 29 size[x]=1; 30 for(int i=h[x];i!=-1;i=b[i].ne) 31 dfs(b[i].v),size[x]+=size[b[i].v]; 32 } 33 inline int minn(int x,int y){ return x<y?x:y; } 34 inline int maxx(int x,int y){ return x>y?x:y; } 35 void build(int x,int z,int y) 36 { 37 t[x].l=z,t[x].r=y; 38 if(z==y) 39 { 40 t[x].v=z; 41 return; 42 } 43 int mid=z+y>>1; 44 build(x<<1,z,mid),build(x<<1|1,mid+1,y); 45 t[x].v=minn(t[x<<1].v,t[x<<1|1].v); 46 } 47 void pushdown(int x) 48 { 49 if(t[x].lz) 50 { 51 t[x<<1].v+=t[x].lz; 52 t[x<<1].lz+=t[x].lz; 53 t[x<<1|1].v+=t[x].lz; 54 t[x<<1|1].lz+=t[x].lz; 55 t[x].lz=0; 56 } 57 } 58 void update(int x,int pos,int sum) 59 { 60 if(t[x].l==pos) 61 { 62 t[x].lz+=sum,t[x].v+=sum; 63 return; 64 } 65 pushdown(x); 66 if(t[x<<1|1].l<=pos) update(x<<1|1,pos,sum); 67 else 68 { 69 t[x<<1|1].lz+=sum,t[x<<1|1].v+=sum; 70 update(x<<1,pos,sum); 71 } 72 t[x].v=minn(t[x<<1].v,t[x<<1|1].v); 73 } 74 int query(int x,int sum) 75 { 76 if(t[x].l==t[x].r) 77 { 78 if(t[x].v>=sum) return d[t[x].l]; 79 return -inf; 80 } 81 pushdown(x); 82 if(t[x<<1|1].v>=sum) return maxx(d[t[x<<1|1].l],query(x<<1,sum)); 83 return query(x<<1|1,sum); 84 } 85 int getpos(int sum) 86 { 87 int le=1,ri=n,mid; 88 while(le<ri) 89 { 90 mid=(le+ri+1)>>1; 91 if(d[mid]>=sum) le=mid; 92 else ri=mid-1; 93 } 94 return le; 95 } 96 int main() 97 { 98 scanf("%d%lf",&n,&k); 99 for(int i=1;i<=n;i++) scanf("%d",&d[i]); 100 sort(d+1,d+n+1,greater<int>()); 101 memset(h,-1,sizeof(h)); 102 for(int i=n;i>=1;i--) 103 { 104 fa[i]=floor(i/k); 105 b[e].v=i,b[e].ne=h[fa[i]],h[fa[i]]=e++; 106 } 107 dfs(0),build(1,1,n); 108 for(int i=1;i<=n;i++) 109 { 110 if(vi[fa[i]]) update(1,bd[fa[i]],size[fa[i]]-1),vi[fa[i]]=0; 111 ans[i]=query(1,size[i]),bd[i]=getpos(ans[i]); 112 update(1,bd[i],-size[i]),vi[i]=1; 113 } 114 for(int i=1;i<n;i++) printf("%d ",ans[i]); 115 printf("%d",ans[n]); 116 return 0; 117 }
T3《秘密袭击》
得分:25
想这题的时间不多,只写了枚举和链上部分。因为数据范围比较小被暴力水过了……我还不会正解,大概是拉格朗日插值模拟NTT过程优化背包什么的,感觉这题有点可惜啊。或许出题人能想到的暴力方法总是带着正解的影子,卡掉所有暴力实在太难了吧。
复杂度大概在$n^3$的暴力有两种思路。一是像正解一样转化成“$k$大值大于等于某个值的联通块个数”,需要枚举这个值做背包,统计答案时累加DP值即可,优化的空间不是很大,但是跑得快的OJ也可以过。二是直接求$k$大值等于某个值的联通块数乘权值统计答案,这又有两种实现方法。用$f[i][j]$表示$i$为根的子树有$j$个值大于等于当前处理的值方案数。如果枚举每个点求它作为$k$大的方案数,可以忽略$f[i][k]$以上的值。如果从大到小枚举每个权值逐个加入树求至少包含$k$个已加入点的背包数,可以通过差分得到这个权值对应的方案数;这种做法能优化的地方很多,比如把大于$k$的方案全都累加到$k$上、DP之前判断这种权值有没有出现 以上是关于HEOI2018题解的主要内容,如果未能解决你的问题,请参考以下文章