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 }
chess-handon
 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 }
chess-100pts

 

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 }
iiidx-handon
  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 }
iiidx-100pts

 

 

T3《秘密袭击》

得分:25

       想这题的时间不多,只写了枚举和链上部分。因为数据范围比较小被暴力水过了……我还不会正解,大概是拉格朗日插值模拟NTT过程优化背包什么的,感觉这题有点可惜啊。或许出题人能想到的暴力方法总是带着正解的影子,卡掉所有暴力实在太难了吧。

        复杂度大概在$n^3$的暴力有两种思路。一是像正解一样转化成“$k$大值大于等于某个值的联通块个数”,需要枚举这个值做背包,统计答案时累加DP值即可,优化的空间不是很大,但是跑得快的OJ也可以过。二是直接求$k$大值等于某个值的联通块数乘权值统计答案,这又有两种实现方法。用$f[i][j]$表示$i$为根的子树有$j$个值大于等于当前处理的值方案数。如果枚举每个点求它作为$k$大的方案数,可以忽略$f[i][k]$以上的值。如果从大到小枚举每个权值逐个加入树求至少包含$k$个已加入点的背包数,可以通过差分得到这个权值对应的方案数;这种做法能优化的地方很多,比如把大于$k$的方案全都累加到$k$上、DP之前判断这种权值有没有出现

以上是关于HEOI2018题解的主要内容,如果未能解决你的问题,请参考以下文章

HEOI 2018林克卡特树

Bzoj 3166 [Heoi2013] Alo 题解

Bzoj 3165 [Heoi2013]Segment题解

bzoj4028 [HEOI2015]公约数数列(分块+卡常?)

题解 P4107 [HEOI2015]兔子与樱花

Heoi2014系列题解