「JOISC 2020 Day1」建筑装饰 4(dp+构造)
一个做法是设(f[i][j][0/1])表示一共选了(j)个A了,第(i)个选了A还是B,是否可行。
注意到可行的(j)其实是一个区间,证明归纳易得。
于是设(f[i][0/1])表示可行的区间,直接dp即可。
我的做法是,假设先不管n个A的限制,从左往右扫一遍,每次选尽量小的,保证有解先。
考虑有些位置的选择可以改变,x改变了,x+1可能要改变,可能不要改变。
如果x改变了,x+1必须跟着改变,x->x+1连一条边,不难发现有若干条链。
每一条链可以选一个后缀,那么用不等式加法原理算一算就好了。
代码:https://loj.ac/submission/769869
「JOISC 2020 Day1」汉堡肉(2-sat+数据结构优化建图)
神题。。。
先说随机乱搞法。
每次先随机k个矩形,然后把其它矩形加入,每次选面积减小比最小的一个。
随便hack,但是它能过数据。
正经做法:
考虑算出(maxl,minr,maxd,minu)
先考虑横坐标。
假设(maxl>minr),否则横坐标放在([maxl,minr])的任意一个位置都行。
在(x=minr)这条直线的左边,至少要放一个点,因为(minr)是一个矩形的右边界,这个矩形在(x=minr)的左边。
又因为这是最左的右边界,所以不如把一个点就放在(x=minr) 上,这样还能覆盖右边的一些矩形。
同理,对(maxl,maxd,minu)可以得到类似的结论。
于是得到结论:
在(k)个点的坐标中,(maxl,minr,maxd,minu)都至少出现了一次。
当(k<=3)时,有鸽巢原理,至少有一个点的两维坐标都是(maxl,minr,maxd,minu)中的一个。
枚举这个点,把它覆盖的矩形去掉,递归到(k-1)的情况。
(k=4)时,先讨论以上的情况,最后的一种情况就是(maxl,minr,maxd,minu)围成的矩形A上,每一条边有一个点。
现在考虑其它要被覆盖的矩形B,如果一个矩形B经过了A的三条边,那一定有一条边是完全覆盖,所以矩形B一定会被覆盖不考虑。
剩下的情况就是:
1.B覆盖A的一个角。
2.B覆盖A的两条对边。
3.B覆盖A的一条边。
4.B和A无交
如果出现4说明每条边放一个(不放交点)无解。
三种情况皆可用一个2-sat模型来说明:
对于一条边上的一个区间:[x,y],建立两个点代表它选or不选,
当同一边上两个区间不交时,则只能选一个。
1,2相当于两个区间至少选一个
3相当于这个区间必须选。
用数据结构(线段树)优化区间不交的连边可以做到(O(n~log~n))。
代码(随机):https://loj.ac/submission/769928
「JOISC 2020 Day1」扫除(线段树分治+平衡树)
对于一开始y随x递增而不递增的部分分,可以发现每次修改的都是一段区间。
用线段树(平衡树)维护,每次先二分出修改的区间,然后区间赋值即可。
对于没有4操作的部分分,发现,对于被移动过至少一次点,它们之间满足y随x递增而不递增。
所以先对每个点求出第一次移动的时间(二维排序问题),或者平衡树在线维护什么时候移动。
在点第一次移动时,丢入一棵维护已经移动过点的平衡树,那棵平衡树做前面说的操作。
有4操作时,发现主要是因为新加入的点没有经过前面修改的洗礼,所以不满足那个性质了。
对于每个询问,相当于求一个点经过一个区间的操作后的结果。
利用线段树分治,把这个区间分成线段树上的log个区间。
这样的话,对于线段树上的一个区间,上面的询问都要经过这个区间的修改,就可以当没有4操作那么做了。
时间复杂度:(O(n~log^2~n))
代码:https://loj.ac/submission/772057
「JOISC 2020 Day2」变色龙之恋(交互题,二分)
如果询问两两点(x,y),答案不为2当且仅当(x,y)满足下面条件之一:
1.l[x]=y
2.l[y]=x
3.c[x]=c[y]
先暴力询问把边都找出来。
如果一个点x度数为1,那么相邻的点就是同颜色的点。
否则,它的度数为3,设相邻点为y1,y2,y3
询问(x,y1,y2),(x,y1,y3),(x,y2,y3)
发现不包含(l[x])的那组答案是1,这样就求出(l[x])了,其它组答案是2。
那么再扫一遍就知道每个点同颜色的点是哪个了。
这个只用询问前两组就知道结果了。
所以,如果能找出边,那么(4n)次询问就能求出答案。
考虑告诉你每个点属于二分图哪一部分的那档数据:
对于一个点x,设S为对面的一个点集,当(query{x,S}≠|S|+1)时,说明x和S有边。
于是,枚举二分图左半的每个点,在右边二分就好了,这样(3~log~n)次一个点。
全部数据不告诉我们二分图的构成,我们想去求,然而做不到。
考虑一个一个点加入。
现在加入点x,求它与之前点的边(之前点之间的边已经知道)。
由于之前点的边已经知道,可以对它们染色,分成两个集合,集合内部没有边。
这个集合可能不是原二分图的一边上的点,但是没有关系,因为它们之间没有边就可以二分x与它们的边。
询问数(le 3n~log~n+2n+4n)
(2n)是试探性询问带来的常数。
Code:https://loj.ac/submission/772404
「JOISC 2020 Day2」有趣的 Joitter 交友(并查集+STL+启发式合并):
做的那天有点乱,没一开始想清楚应该维护哪些东西,所以没调出来。
首先,如果x和y之间互相右边,那么把它们缩成一个并查集里,因为其他点到这个并查集的一个点有边相当于到这个并查集的所有点都有边(反过来不成立)。
考虑现在加入一条边x->y:
先取y为y的并查集祖先。
1.如果x这个点到y并查集已经有边了,那么这条边就不用加入了。
所以维护(set~s1[x])表示x这个点能到哪些并查集。
2.如果y并查集到x的并查集有边,那么x,y应该merge。
求并查集之间边的条数应记录一个(map<int,int>~bz[N])
在merge的时候,比如是把(x)并查集并入(y)并查集。
首先需要修改一下(x,y)之间的(bz),顺便处理一下答案的影响。
然后,对于(xin s1[z]),应该改成(yin s1[z]),为了找到这些(z),应该记录(set~s2[x])表示并查集(x)的入点。
在处理(s1[z])时,顺便改一下(z)到({x,y})对答案的影响,同时如果(y->z所属并查集)本来有边,应递归merge。
(x,y)的merge应用启发式合并,即(bz[x].size+s2[x].size<=bz[y].size+s2[y].size)
有一个问题,就是(s2[y])中由(x)来的边不好删除,为了删除,可能需要额外再记录一下东西,但是我用了lazy删除。
即先不删,放在那里,到枚举(zin s2[y])时,如果y,z在一个并查集,那么就忽略,为了统计答案需要记录数组表示这样的边数。
反正这就是个细节讨论题。
代码:https://loj.ac/submission/772330
「JOISC 2020 Day2」遗迹(计数dp)
一道很agc的dp题。
先考虑如何快速求最终剩下的点:
for(int i = 2 * n; i >= 1; i --) {
int x = cho[i];
while(x > 0 && !us[x]) x --;
us[x] = 1;
}
意思就是从大到小枚举每个点,看看每个点会停在哪儿。
有了这个可以dp,顺序肯定是从大到小。
问题是现在相当于us有很多段1,不可能记录每一段,事实上我们就记录第一段连着us[1]的长度,之后的段后面再填。
设(f[i][j])表示选择了(>=i)的,(us[1..j]=1,us[j+1]=0)的方案数。
考虑由(f[i+1][...]->f[i][...])
若(i)不需要保留:
那么它会放在(j)个中的一个,问题是这个系数也不好求,因为不知道哪些地方可以放。
此时更改一下题目的定义:每个地方放的两个有顺序关系,这样答案最后(÷2^n)。
这样的定义下,一共有(j-outA)个空位(outA是已经不保留的个数),系数就是这个。
若i需要保留:
1.它会停在(>j+1)的位置,那这个之后到那里时再考虑。
2.它停在(j+1),那么再枚举一个(k),表示(us[j+2..j+k]=1,us[j+k+1]=0)
系数就是((k+1)*C_{inA-j}^{k-1}*g[k-1])(inA是已经保留的个数)
(g[n])表示n个数,加入后使(us[1..n]=1)的方案数。
(g[n])可以通过上面那个dp类似的方法自我更新出来,但有一种更简单的方法:
记(cnt[j]=sum[cho[i]=j]),
(g[n]=forall i<=n (sum_{j=1}^i{cnt[j]})le i)的方案数
所以设(dp[i][j])表示前i个位置,(cnt)的和(=j)的方案数。
(dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*2*j+dp[i-1][j-2]*j*(j-1))
(g[n]=dp[n][n])
代码:https://loj.ac/submission/772714
「JOISC 2020 Day3」星座 3(笛卡尔树+线段树合并或树形dp)
对于一个区间([l_i,r_i]),找到最高点(a[x])。
这个区间只能有一个(>=a[x])的点。
暴力就是先建出笛卡尔树:
设(f[i][j])表示([l_i,r_i])里选的最高点是(j)的最大和。
转移有:
x这里选一个点j0,左区间选一个j1,右区间选一个j2。
可以一个接一个合并,复杂度(O(n^2))。
用线段树合并优化一下就(O(n~log~n))
另一种常数更小的做法:
考虑把笛卡尔树建成一个树形关系。
如果在x这里选了一个y,选y就相当于覆盖了x到x的一个祖先(z(a[z]<=y且a[fa[z]]>y))。
可以倍增求出(z)。
问题变为一棵树上有若干祖先后代链,选出不相交的若干链,使权值和最大。
经典问题(不是祖先后代链也能做),只需要dfs序+树状数组实时维护一个点到根的dp值的和就差不多了。
代码(线段树合并):https://loj.ac/submission/772405
「JOISC 2020 Day3」收获(基环内向树+线段树合并):
考虑对每一个点求出(fa[i])表示i取了一个果子后,下一个取这个果子的是谁。
因为每个点一条出边,所以是个基环内向树。
再处理一下每个果子第一次被谁吃,然后把每个果子的时间改为被第一个人吃的时间+到树根的时间。
用个线段树合并再查询一下得到树上的答案。
接着考虑环上的答案。
先破环为链,记一个前缀和数组(s[x])表示从(x)走到1的答案,设环长是(len)。
(x,y)都是环上的点:
假设(y)子树里的一个果子到(y)的时间是(T1),而(x)处有一个询问(T2)。
设(v1=T2-s[x]-[x>y]*len)
(v2=T1-s[y])
那么贡献就是:([v1>=v2]*(lfloor {(v1-v2)over len} floor+1))
看似需要三维偏序,因为多了个([x>y]*len),但由于这里的贡献是(len),除了分母之后也就是1,可以讨论一下变成二维偏序,细节略,我这里用了三棵线段树。
Code:https://loj.ac/problem/3278
【JOISC 2020】迷路的猫(通信题,bfs序、构造):
考虑k>=3时,如果一棵树,那么对每条边i->fa[i],染上dep[i]%3.
这样的话,询问时,会得到x和(x+1)%3有值,走x就好了。
如果多了些其它边,考虑建出图的bfs序的生成树(一直想dfs序没想出来),那么多的边两个点也一定深度相邻,同样的标号方法即可。
k=2、B=6时。
深度为1的点只有一个方向,走就好了。
深度为3的点x,染色保证x->fa[x]和z->fa[z]=x的颜色不一样,同时在走的过程中记录上一次的边的颜色,这样,只出现一次的颜色就是要走的方向。
剩下的问题一开始在链上就要判断方向。
考虑构造一个循环串S,使得S顺时针看长度为x的子串和逆时针看长度为x的子串都不一样。
这样,只要先走x步,看看是顺时针出现在S中还是逆时针,就可以判断方向,在判断了方向后,不走来的边即可。
如果走x步就会最多多花2x步,所以2x<=B。
S、x可以爆搜,发现不管怎么样找不到x<=3的解,能找到一个x=5的解,这样可以拿到部分分。
事实上,走x步,算上起点的另一条边和终点的另一条边,我们一共知道了5条边的信息,
所以只用3步就好了。
特别注意染色是要同时满足两个条件。
代码:http://uoj.ac/submission/389005
「JOISC 2020 Day4」首都城市(数据结构优化建图+tarjan 或 点分治+bfs)
做法1:
如果颜色i的虚树上有颜色j的点,i->j连一条边。
建出图后缩强联通分量,出度为0且点数最小的分量就是答案。
用倍增或者树链剖分优化这个建图即可做到(O(n~log~n))
做法2:
考虑点分治,每次强制选分治中心,求选它的答案。
选它的答案可以用bfs实现,具体为:
一开始加入分治重心的颜色到队列里,每次取队列头的颜色,枚举这个颜色的所有点x,开始跳父亲,把路过的颜色加入队列,直到跳到一个已经在虚树上的点。
注意是只在分治子树内bfs,因为如果出去了,就说明要经过更高的分治重心,那么在那个时候就统计答案了。
这个复杂度也是(O(n~log~n)),常数更小。
代码(树链剖分优化建图):https://loj.ac/submission/774428
「JOISC 2020 Day4」治疗计划(数据结构优化建图+dij)
发现找不到状态,不如从左往右依次确定(注意时间无序,只考虑区间上的变化):
设(f[i])表示(1-r[i])都搞定了的最小代价。
注意这个dp里,时间是无序的,我们只考虑的是能不能
(f[i])能转移到(f[j])当且仅当:
(r[i]-l[j]+1ge|t[i]-t[j]|)
讨论(t[i],t[j])的大小关系,用线段树优化建图跑dij。
时间复杂度:(O(n~log^2~n))