【IOI2018】组合动作(构造)
一种思路是:注意到后面的字符都和第一个字符不同,于是对每一位进行2次询问(不用3次,因为剩下的字符可直接确定)确定字符串,不太理想。
但是给询问的字符串长度<=4*n,这启示我们进行并行询问。实际上,假设现在答案字符串为S,先找到首位字符,询问$SAASABSACSB$(A,B,C是除了首位字符的字符)就能知道下一位的结果。(可以看代码)最后一个暴力即可。
但是首位字符需要3次询问,可以二分一下字符集,就可以降到2次询问。
#include#include "combo.h" using namespace std; #define p press string guess_sequence(int n) { string r[3], a; if (p("AB")) { if (p("A")) { r[0] = "B"; r[1] = "X"; r[2] = "Y"; a = "A"; } else { r[0] = "A"; r[1] = "X"; r[2] = "Y"; a = "B"; } } else { if (p("X")) { r[0] = "A"; r[1] = "B"; r[2] = "Y"; a = "X"; } else { r[0] = "A"; r[1] = "B"; r[2] = "X"; a = "Y"; } } if(n==1)return a; for (int i = 2; i < n ; i++) { int v = p(a + r[0] + a + r[1] + r[0] + a + r[1] + r[2] + a + r[1] + r[1]); if (v == i+1) a += r[1]; else if (v == i) a += r[0]; else a += r[2]; } if (p(a + r[0])==n) a += r[0]; else if (p(a + r[1])==n) a += r[1]; else a += r[2]; return a; }
代码不合我的码风是因为在loj上格式化了。
【IOI2018】排座位(线段树)
这道题不能用$\max-\min=r-l$的传统思路。
先考虑$H=1$时怎么做。在线段树上每个叶子节点都维护"把当前值染黑,有多少个黑色连续段",则答案就是区间最小值个数。(只有0组成的区间显然满足条件,节点的值为1(也是最小值),所以询问区间最小值可以得到正确结果)
把位置相邻的点连边。则每个位置的值是点-边数。
考虑交换操作。由于在区间$[1,l]$和$[r,w]$的节点,这两个点同时被染白/染黑,所以不用计算贡献。如果点在区间$[l,r]$则要计算贡献。可以发现节点的变化情况都是$l$变白,$r$变黑,都是反转颜色。
可以把原来的贡献删除并加入现在的贡献。具体来说,假设一个黑变白的点是$x$旁边是$y$则当$x$是黑的且$y$是白的时候要删除贡献。就是一个区间减法。加入贡献同理。
回到原题,在线段树上每个叶子节点都维护"把当前值染黑,有多少个黑色矩形",则答案就是区间最小值个数。(只有0组成的区间显然满足条件,节点的值为1)
实际上,这个连续段的个数等于黑点挨着>=2的白点的点个数+白点挨着超过2个黑点的点的个数。
在加入黑点时可以删除原来的贡献并且加入新的贡献。方法类似。
#includeusing namespace std; #define N 4000010 int mn[N],s[N],va[N],bz[N],n,m,mt[N],v,q,ss[N]; #define id(x,y) (x-1)*m+y void bd(int o,int l,int r){ if(l==r){ mn[o]=va[l]; s[o]=1;return; } int md=(l+r)/2; bd(o*2,l,md); bd(o*2+1,md+1,r); mn[o]=min(mn[o*2],mn[o*2+1]); s[o]=0; if(mn[o]==mn[o*2])s[o]+=s[o*2]; if(mn[o]==mn[o*2+1])s[o]+=s[o*2+1]; } void pd(int o){ bz[o*2]+=bz[o]; bz[o*2+1]+=bz[o]; mn[o*2]+=bz[o]; mn[o*2+1]+=bz[o]; bz[o]=0; } void mod(int o,int l,int r,int x,int y,int v){ if(x>y||r return; if(x<=l&&r<=y){ mn[o]+=v; bz[o]+=v; return; } pd(o); int md=(l+r)/2; mod(o*2,l,md,x,y,v); mod(o*2+1,md+1,r,x,y,v); mn[o]=min(mn[o*2],mn[o*2+1]); s[o]=0; if(mn[o]==mn[o*2])s[o]+=s[o*2]; if(mn[o]==mn[o*2+1])s[o]+=s[o*2+1]; } struct no{ int x,y; }a[N]; int tx[4]={0,-1,0,1},ty[4]={-1,0,1,0}; int in(int x,int y){return x>0&&x<=n&&y>0&&y<=m;} int m1(int x){ int mn=v+1; if(in(tx[0]+a[x].x,ty[0]+a[x].y)) mn=min(mn,mt[id(tx[0]+a[x].x,ty[0]+a[x].y)]); if(in(tx[1]+a[x].x,ty[1]+a[x].y)) mn=min(mn,mt[id(tx[1]+a[x].x,ty[1]+a[x].y)]); return mn; } int m2(int x){ int r1=v+1,r2=v+1; for(int i=0;i<4;i++) if(in(tx[i]+a[x].x,ty[i]+a[x].y)){ if(mt[id(tx[i]+a[x].x,ty[i]+a[x].y)]<r1) r2=r1,r1=mt[id(tx[i]+a[x].x,ty[i]+a[x].y)]; else if(mt[id(tx[i]+a[x].x,ty[i]+a[x].y)]<r2) r2=mt[id(tx[i]+a[x].x,ty[i]+a[x].y)]; } return r2; } int main(){ scanf("%d%d%d",&n,&m,&q); v=n*m; for(int i=1;i<=v;i++){ scanf("%d%d",&a[i].x,&a[i].y); a[i].x++; a[i].y++; mt[id(a[i].x,a[i].y)]=i; } for(int i=1;i<=v;i++){ va[i]=va[i-1]; if(m2(i); if(m1(i)>i)va[i]++; for(int j=0;j<4;j++) if(in(tx[j]+a[i].x,ty[j]+a[i].y)){ int v=mt[id(tx[j]+a[i].x,ty[j]+a[i].y)]; if(v; else if(v>i&&m2(v)==i)va[i]++; } } bd(1,1,v); while(q--){ int x,y,ct=0; scanf("%d%d",&x,&y); x++; y++; if(x>y)swap(x,y); if(x==y){ printf("%d",s[1]); printf("\n"); continue; } ss[++ct]=x;ss[++ct]=y; for(int i=0;i<4;i++) if(in(tx[i]+a[x].x,ty[i]+a[x].y)) ss[++ct]=mt[id(tx[i]+a[x].x,ty[i]+a[x].y)]; for(int i=0;i<4;i++) if(in(tx[i]+a[y].x,ty[i]+a[y].y)) ss[++ct]=mt[id(tx[i]+a[y].x,ty[i]+a[y].y)]; sort(ss+1,ss+ct+1); for(int i=1;i<=ct;i++) if(ss[i]!=ss[i-1]){ if(m2(ss[i])<ss[i]) mod(1,1,v,max(m2(ss[i]),x),min(ss[i],y)-1,-1); if(m1(ss[i])>ss[i]) mod(1,1,v,max(ss[i],x),min(m1(ss[i]),y)-1,-1); } swap(mt[id(a[x].x,a[x].y)],mt[id(a[y].x,a[y].y)]); swap(a[x],a[y]); for(int i=1;i<=ct;i++) if(ss[i]!=ss[i-1]){ if(m2(ss[i])<ss[i]) mod(1,1,v,max(m2(ss[i]),x),min(ss[i],y)-1,1); if(m1(ss[i])>ss[i]) mod(1,1,v,max(ss[i],x),min(m1(ss[i]),y)-1,1); } printf("%d",s[1]); printf("\n"); } }
【IOI2018】狼人(可持久化线段树,Kruskal重构树,dfs序)
由于在人形态不能经过$ 同理可以建出边权为$\min(x,y)$(x,y为边的两端点标号)的kruskal重构树。 在判定是否能变身时,可以在重构树上dfs一下得到dfs序,把所有点视为平面上的一个点$(dfn1_x,dfn2_x)$,则查询时倍增到对应的点,则倍增到的位置的子树的所有节点是当前点可以走的。判定是否有点在这2个区间构成的矩形即可。数点可用离线+BIT/可持久化线段树解决 【IOI2018】机械娃娃(线段树,构造) 题目中给了$2$-开关,可以考虑把它扩展到$k$-开关 可以使用线段树构造。构造一颗线段树,大小为$m$(m为最小的$2$的次幂使其>=关键点个数)。叶子节点连向对应的关键点,如果一个点没有连向关键点(叶子节点超出需求),就把它连向根。这样子最坏情况要$2*n$个开关,不太理想。 注意到,如果一个点子树的所有叶子节点都被连到根,则可以把这个子树除当前节点的其他节点删除,并且把当前点连向根。但是由于访问到的顺序是fft的rev数组,复杂度没有保证,连的效果不好。 实际上,可以把线段树的右边n-关键点个节点空出来,类似区间定位一样找到$\log_2 n$个节点,把它们的子树都删掉,把这$\log_2 n$个节点都连到根。 没被空出来的节点按顺序连到关键点即可。这样子就可以把开关数降到$n+\log_2 n$ )
rv[i]=(rv[i>>1]>>1)|((i&1)<<(l-1));
memset(tp,127,sizeof(tp));
for(int i=p-n;i i;
int ct=0;
for(int i=0;i )
if(tp[i]<2e9)tv[tp[i]]=v[++ct];
int rt=bd(0,p-1);
c.push_back(v[0]);
for(int i=0;i 【IOI2018】高速公路收费(二分,最短路) 先询问一下得到原图的最短路。 考虑在图上二分出一条最短路的边。(不能二分出点,否则只能拿最多$90$分。如果二分出点拿到$100$分请告诉笔者。) 二分的方法是:(下文设md为区间终点)把左端点$l$到$md$的边设为拥堵边,检测询问的值是否等于原图的最短路。如果是则右移左端点否则左移右端点。 如果把$[l,md]$设为拥堵边后询问的值不是最短路,则所有最短路上的边都在$[l,md]$里面。 如果把$[l,md]$设为拥堵边后询问的值是最短路,则不是所有最短路的边都在$[l,md]$中。 (咕咕咕) 代码不合我的码风是因为在loj上格式化了。 【IOI2018】会议(线段树,dp) 由题意可以得到一个dp方程:设$f_{l,r}$表示$[l,r]$区间最小代价,由题意$f_{l,r}=\min(f_{l,md-1}+(r-md+1)*h_{md},f_{md+1,r}+(md-l+1)*h_{md})$,其中$md$为最小值所在位置。 但是这样子效率太低。只能过$19$分。不能直接把$dp$数组计算出来,而要根据询问减少计算量。 根据套路考虑建出原序列的笛卡尔树,可以用rmq建,然后把每一个询问挂在笛卡尔树对应的点上。这样子的好处是每次处理的$mid$都是一定的。 对于询问$[l,r]$只和$f_{l,md-1}$和$f_{md+1,r}$有关。且这2个dp数组有一个端点是$md-1$或$md+1$。对于下面的子任务,笛卡尔树不高,所以可以暴力算。这样子可以拿到$60$分。 所以可以考虑用线段树维护$f_{l...md-1,md-1}$和$f_{md+1...r,r}$的值($l,r$是当前分治区间不是询问区间),对于每一个询问单点询问就可以直接得到答案。 考虑做完当前点后维护$f_{l...r,r}$和$f_{l,l...r}$的值。考虑计算$f_{l...r,r}$的值的过程。可以观察原dp方程,可以注意到当$r$向右移一位时,左边的项会增加$h_{md}$,右边的项会增加$f_{md+1,r}-f_{md+1,r-1}$。一边的贡献是一次函数,但是可以不用李超树维护。由于笛卡尔树的性质,区间$[md+1,r]$的最小值小于等于$md$的最小值。所以右边的增量$\leq md$。所以方程取左边还是右边有一个分界线,可以二分出这个分界线然后更新线段树(就是区间一次函数覆盖/区间加)。(其实不用二分。只要维护线段树左/右端点处的值,当左边比右边端点取方程同一边时直接改,否则继续递归)。 然后上传到上面的区间。这样子上传没有问题。先讨论一下以当前右端点为$r$的dp数组(左端点为$l$的数组相似),是因为如果新的最大值位置是$r+1$,则现在更新的dp值恰好可以用于更新上面的询问。如果新的最大值位置为$l-1$,则不用更新上面的询问。由于笛卡尔树上当前点的子节点已经被算完了,所以不会对当前点的子节点产生影响。 自此,我们在$O(n\log_2 n)$的时间内离线解决了这道题。如果硬要在线只要把线段树可持久化一下即可。 #include
#include
#include