杂题乱做

记录一些有意思的题。

其他模拟赛link

CF1858E2

\(*2600\).

维护一个当前指针endpos,指向序列末尾,同时维护一个 set<pair<int,int> >,表示每个数第一次出现的位置。

加操作可以直接加入,如果当前这个数已经出现过直接右移指针,否则维护一棵树状数组,加上贡献。

减操作直接将 endpos 左移即可。

回滚操作时,因为可以记录上一次操作,然后就可以直接减操作变成加,加操作变成减。

询问直接在BIT上查就行。

注意细节。

CF981G

*2500.

感觉很显然啊。

考虑对于每个可重集,添加怎么维护 size。对于一个可重集,如果这个数存在,那么 size 乘二,否则 size 加一。

因为是对一段添加并且值域为 \(n\),所以容易想到用 ODT 来维护数 \(x\) 是否存在。如果某一段的值为 0,就对这一段区间加。否则,就区间乘 2。线段树实现即可。

容易犯错的是没有保证 ODT 的复杂度正确,即在 ODT 上跳完之后没有将遍历的区间推平。

代码很好写。

CF1548B

*1800.

双指针。差分之后维护一个 gcd ST表,然后判 \(\gcd _{i=l}^r\not= 1\) 即可。

据说叫什么不删除双指针?

CF375D

*2400.

简单树上莫队。

CF1400F

*2800.

lzqy_讲状态数 dp 的例题,好像秒了。

发现这个 \(x\) 很小,想一下哪些状态和 X-substring 有关,直接把需要的状态存下来,而这是很对的。

CF1922F

*2500.

区间 dp.

看到 \(n,x \le 100\)\(\sum x\le 500\) 发现可以 \(O(n^3x)\)

很奢侈地设 \(f_{i,j,k}\) 指令区间 \([i,j]\) 全部为 \(k\) 的代价。

发现这个不包含 \(k\) 覆盖的操作很难转移,考虑再设一个 \(g_{i,j,k}\) 表示令区间 \([i,j]\) 不含 \(k\) 的代价。

考虑怎么转移。

发现 \(g\) 的转移就是一个区间 dp 板子,发现 \(g_{i,j,k}\) 要对所有 \(({g_{i,j,o}+1})(1\le o \le x)\)\(\min\)

然后对着 \(f\) 区间 dp 一下,算上 \(g\) 的贡献即可。

需要注意什么时候需要对 \(\min(g_{i,j,k})\) 加一,不然可能会 WA on #2。

CF1746F

*2800.

首先知道是一个 \(\log\) 级别的。

考虑一个神秘哈希,如果一段区间合法,那么你对每个数值进行哈希,这段区间哈希值的和必定是 \(k\) 的倍数。注意到这只是充分条件,不能反推,所以多哈希几次就行。

常数太大不想调。

upd:调用太多次函数且不内联且早期丑陋马蜂导致的。重构完就过了。

upd2:过段时间又交一次又不过,发现原因是 umap 常数太大/fn,直接将原来的值改为指向离散化数组的位置就跑的飞快了。

P9461

感觉很神。

先有一个引理:b 中区间 \([l,r]\) 的最小众数必定为 \(b_l\)\(1\)。用反证法证明。

然后对 \(lt=2,3,\dots,\max a\) 分别计算对应的最小众数为 \(b_{lt}\)\(rt\) 数量。其余的为最小众数为 \(1\) 的区间。

问题转化为计算对应的最小众数为 \(b_{lt}\)\(rt\) 数量。考虑一段 a 中区间满足能从 b 的 \(lt\) 走到 \(rt\) 当且仅当\(\forall x\in[lt,rt],a_x\ge b_{lt}\)。用链表维护答案。

CF1186C

*1800.

考虑移动一位,造成影响的只有最左边和最右边的。直接前缀和。

CF1575D

*1800.

对每个 25 的倍数暴力 chk 就行。

X 可以直接暴力赋值。

CF1270C

*1400.

考虑把所有数的异或和放到序列里面,这样异或和就是 0。然后补足条件就行。

CF1799H

*3200.

考虑保留子树和删掉子树没有本质区别。

状压,记 \(f_{u,i,s}\) 表示以 \(u\) 为根的子树,子树内最早的保留子树操作是 \(i\),操作的是以 \(u\) 为根的子树的子树的集合为 \(s\)

合并两个子树的时候,要求至多有一个子树满足 \(i\le k\),且两个子树的集合 \(s1,s2\) 满足 \(s1\cap s2=\emptyset\)

在合并完所有儿子后,再考虑操作 \(u\) 的子树,枚举操作。
如果要保留子树,则要求 \(i>j\)。如果是一个删除子树的操作,则要求 \(j\forall l\in s\)。同时需要判断子树大小是否正确。

CF505C
*1800.

考虑每次步数的变化量是不大的,所以可以计数的时候枚举对于原来的变化量,转移即可。

CF1157F

*2000.

记子集为 \(res\),最大值为 \(x\),最小值为 \(y\)

考虑答案的情况。看样例不难发现答案一定形如 \((x,\cdots,x),(x-1,\cdots,x-1),\cdots,(y,\cdots,y),y+1,y+2,\cdots,x-1\)

考虑这样的形状有什么限制。首先发现这一段区间是值域连续的。其次,除了 \(x,y\),其他的元素出现次数必须 \(\ge 2\),而且肯定能选就选。因为值域较小,因此可以直接在桶上双指针,每次尽量向右扩展找到最大的出现次数 \(\ge 2\) 的位置,更新答案即可。

需要注意 if 语句的判断顺序不要出错。

CF1638E

*2400.

考虑显然可以套个 ODT。拆一下单点的值的影响,发现修改的时候用 BIT 加一下就行。

CF1209G1

*2000.

发现修改一段前缀必定是保留这段前缀里面出现次数最多的,但是需要先把这个数的所有地方包含,否则很劣。考虑从前开始贪心,每次向后跳之后找到出现次数最多的数,这个数就保留下来。

CF1416C

*2000.

肯定能建出 01-trie。给每个位置算贡献。从 trie 树往下贪心,钦定前面的相同然后往下找,记子树个数分别为 0 和 1,分讨一下。

CF843B

*2000.

随机化,随机若干个点找到 x 的前驱,然后直接暴力跳就可以了。

CF675D

这纯唐氏题。

CF863E

*2000.

一个段能被覆盖的充要条件是这个段每个位置被覆盖次数都 \(\ge 2\)

差分一下。

注意差分的时候需要把 \(l,r,r+1\) 都加入数组,因为这样才能保证差分不出错。

CF1956D

*2000.

考虑一段区间 \([l,r]\) 一定可以被全部推平成 \(r-l+1\),构造类似 \(0,1,2,\dots,r-l\) 即可。

又因为 \(n\le 18\),可以 \(2^n\) 枚举所有位是否被推平,找出最大值。

因此考虑如何输出。考虑构造出形如 \(0,1,\dots,x\) 的形式必定是对 \(0,1,\dots,x-1,t\) 做一次区间推平,然后再把前面一段改回 \(0,1,\dots,x-1\)

因此可以递归地进行处理,处理 \([l,r]\) 时先递归 \([l,r-1]\),然后更改 \([l,r]\),再更改 \([l,r-1]\)。需要注意,在每次操作之前先把每一位归零,区间覆盖完 \([l,r]\) 之后先把 \([l,r-1]\) 清零。

CF1451E2

首先肯定可以先用 \(n-1\) 次异或操作,求出 \(a_1\) 和其他位置的异或值,这样只需要知道 \(a_1\) 就可以知道所有的值。

于是问题转化为怎么在两次操作内用三种操作求出 \(a_1\)

首先如果有两个位置的异或值是一样的,说明这两个位置的值是一样的。于是对这两个位置做一次 AND 或者 OR 就可以知道值,进而推出所有值。

其次,因为所有位置的异或值都不一样,说明每个值出现了 恰好一次。考虑如果 \(x\oplus y=1\),说明 \(x\)\(y\) 只有最低位不同。因此找出这个位置,求出 \(a_1\) 除了最后一位的值。那么求最后一位的值只需要找其中一个异或值的最后一位为 \(0\) 的位置就可以了。

signed main() {
    int n; rd(n);
    t[0]=1;
    for(int i=2; i<=n; i++) cout<<"XOR 1 "<<i<<endl,cin>>c[i],t[c[i]]++;
    for(int i=0; i<n; i++)
        if(t[i]>=2) {
            int w1=-1,w2=-1;
            for(int j=1; j<=n; j++)
                if(c[j]==i) {
                    if(w1==-1) w1=j;
                    else {
                        w2=j;
                        break;
                    }
                }
            assert(w1!=w2);
            cout<<"AND "<<w1<<' '<<w2<<endl,cin>>a[w1];
            a[1]=a[w1]^c[w1];
            cout<<"! "<<a[1]<<' '; for(int i=2; i<=n; i++) a[i]=a[1]^c[i],cout<<a[i]<<' '; cout<<'\n';

            return 0;
        }
    int w=0;
    for(int i=2; i<=n; i++) if(c[i]==1) w=i;
    int res1;
    cout<<"AND 1 "<<w<<endl,cin>>res1;
    int res2;
    for(int i=2; i<=n; i++) if(c[i]==2) w=i;
    cout<<"AND 1 "<<w<<endl,cin>>res2;
    assert(res1+res2%2==res1|res2);
    a[1]=res1+res2%2;
    cout<<"! "<<a[1]<<' '; for(int i=2; i<=n; i++) a[i]=a[1]^c[i],cout<<a[i]<<' '; cout<<'\n';
    return 0;
}

嘴巴会的:

CF444D

根号分治。

字符串哈希之后转化题意为:

长度为 \(n\) 的序列,\(q\) 次询问求最短的包含数 \(x,y\) 的区间长度。

这很根号分治。设阈值为 \(B\)

如果 \(x,y\) 出现次数均小于 \(B\),那么直接类似归并,在两个 vector 里面扫一遍即可。

否则出现次数 \(\ge B\) 的数 \(p\) 显然 \(\large\le \frac n B\)个。考虑一个暴力,从头到尾扫一遍,发现一个数\(q\)\(p\) 的贡献要么是在 \(p\) 前且和 \(p\) 最近的 \(q\) ,要么是后面的且离 \(p\) 最近的 \(q\)。所以直接记录一下就可以了。所以和其他的匹配是 \(O(n\sqrt n)\)的。令 \(B=\sqrt n\),丢到一个 umap 里面就做到了纯正的 \(O(n\sqrt n)\)

ps:lsy提出在存 \(q\) 的位置里面二分,然后调一下 \(B\) 就可以 \(O(n\sqrt {n\log n})\)。感觉好写一点啊。

代码有点难写,但是嘴巴会了就是会了!!!1

posted @ 2024-02-29 13:45  lgh_2009  阅读(6)  评论(0编辑  收藏  举报