这里会放我写过的题的题解。

1|02024-7-7

  • 修复公路
    题意:给定n个点和m个边以及边连接的两个点和边的价值,让你选任意条边使得点都联通且选中的边的最大值最小。
    分析:全部联通,选边最少的肯定是使其成为一棵树,又要边的最大值最小,所以就很自然的想到可以先把边按值排序,然后从小到大一次判断要不要选这个边(如果这个边是必要的树边就连,不是就不连),要想判断是不是树边,可以用一个并查集搞定,如果两点本就在同一集合,那就不必连,反之则连
    总结:刚来重庆,心态有点不好,做做水题稳住心态。

  • 无序字母对
    题意:给定n个字母点对,每个点对的两个点的位置可以互换,让你求一个长度为n+1的字母序列使得包含所有给出的点对(点对互换位置与否都可以)要求序列字典序最小
    分析:一看到这道题的时候毫无头绪,但是手磨一便样例就会发现这些合法的数据中的样例只需要将每次的点对连无向边,最后就会得到一个环,这个也很好证明:如果不是一个环那么必然有的地方会断掉那也就不能完成“包含所有点对”的要求了。所以只需要把点对连无向边,若不是环就无解,若是那就从字典序最小的一个开始跑一边欧拉路就行了
    总结:题的思维量不大,但是需要想的细节有点多,而且需要熟悉怎么打欧拉路的板子我就是忘了怎么打板子才半天没做出来,这题是6t给我的jzp学长讲课的课件,做完我才发现这一页上面就是欧拉路/kk,要是看仔细点没准能写的更快。

  • Phoenix and Earthquake
    题意:给一张n个点m条边的无向连通图,每个点有点权,再给定一个数x,如果图上的边(u,v)连接的两点的点权和大于等于x,那么就可以把他们缩成一个点,这个点的值就是(au+avx),最后问你能不能把图缩成一个点以及缩点顺序。
    分析:乍一看没什么头绪,但是我们很轻松就可以判断出来什么时候输出NO,即为所有点的点权和小于x(n1)的时候或者有点不连通的时候。那么我们就可以继续深入思考,发现其实这张图我们要的只是其中的一棵树而已,因为如果图已经满足了x(n1)的状况,那么如果当前点大于等于x,那么就可以合并其与其在图的生成树的父亲点那么原图也显然满足YES的要求,反之就直接断掉此边,原图依然符合要求,所以方法就出来了,可以找图的生成树然后去按照上面的两步以此判断就可以了,输出的顺序就是可以合并的在前,正着输出,不能的在后,倒着输出,中间再用并查集判断边是否需要合并就行了
    总结:这又是6t给我的题,果然都不是水题啊%%%难度感觉好大,已经是极限了(好吧我的图论还是太差了,写完之后怎么交都是不对,结果最后是没开long long((以后写题先开long long

2|02024-7-8

  • Equal LCM Subsets
    题意:给定两个集合AB,要求从中分别选择两个非空子集SAA,SBB使得SAlcmSBlcm相同,如果无解输出NO
    分析:可以发现如果lcm相等,那么两个集合分解完质因数之后肯定相等,所以可以考虑记录下来所有的分解了的质因数出来然后做,但是发现困难重重,所以考虑通过删掉不合法的数字来构造最后的答案,可以发现一个数被删当且仅当这个数处以这个数与另一组的所有数的gcd都大于1(也就是说它有一个质因数另一组所有数都没有),此时我们将它删掉,于是这题就可做了,但是复杂度显然是很错的,没办法通过,所以考虑优化,发现可以通过线段树来将它的每个叶子节点都表示为这个数处以这个数与另一组的所有数的gcd,这样就可以做到log的修改和查询了,然后就可以愉快的通过了。
    总结:纯纯死人题,难死了,今天讲的题都太难了/kk,而且这个题的数的值都很大,要用__int128存,但是弱智的我都不知道这玩意怎么用,所以就在这个上面花了很多时间((
  • Number of Components
    题意:长度为n的序列中,如果任意点对(i,j)满足i<jai<aj的话那就把这两个点连边,每次修改可以把一个数的值改变,问每次修改之后有多少个连通块。
    分析:可以通过样例等发现一个连通块在序列中是连续的,所以我们只需要找到 minj=1iaj>maxj=i+1naj 的i的个数就行了,但是我们发现这很慢,于是可以想到把小于等于 ai 的变成1,大于的变成0,如果序列是11110000……那么就合法,这样就可以支持用线段树维护10的出现的次数了。
    总结:死人题,不看题解根本不会。

3|02024-7-9

  • 说谁及(sausage)
    题意:给你一颗树,你要将树上a个点中每个点从1a编号,b个点从-1-b编号(a+b=n),使得每次任意选择树中正数或负数的绝对值最小的数,使得删掉这个点以及这个点的所有连边之后树上其他的点依旧联通(n1e5)
    分析:不难发现有一种情况是先把正数全选了再把负数全选了,可以看出这种情况如果满足,那么别的选择情况也一定可以满足(因为别的情况的选正数或负数都可以抽象成前面的情况),所以我们就针对这种情况开始讨论。若要删点之后使树联通,那删的点只能是入度为一出度为零的点(叶子)或者出度为一入度为零的点,而且我们要满足可以先把所有正或负数都删掉的情况,所以不难想到所有正数在树中是一个连通块,所有负数在树中是一个连通块,于是我们就可以枚举边,找到一个边使得它连接的两个点中以一个点为根的子树的size==asize==b,然后递归地把这个点进行从叶子到这个点的顺序赋值就行了。
    总结:考试T1,赛时想出来正解了但是螳臂了,在调代码的时候把前面的判断改了但是忘了改判断完的往函数里的传参了,直接爆弹/kk
    贴个代码。
#include <iostream> using namespace std; const int N = 1e5 + 10; int n, a, b, na, nb; int tot, cnt; int head[N * 2], sz[N], ans[N]; struct zx{int to, nxt;} e[N * 2]; struct rs{int u, v;} p[N * 2]; void add(int u, int v) { e[++tot] = {v, head[u]}; head[u] = tot; } void dfs1(int u, int fa) { sz[u] = 1; for(int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if(v == fa) continue; dfs1(v, u); sz[u] += sz[v]; } } void dfs2(int u, int fa, int op) { for(int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if(v == fa) continue; dfs2(v, u, op); } if(!op) ans[u] = ++na; else ans[u] = -1 * (++nb); } int main() { cin >> n >> a >> b; for(int i = 1; i < n; ++i) { int u, v; cin >> u >> v; add(u, v), add(v, u); p[++cnt] = {u, v}; p[++cnt] = {v, u}; } dfs1(1, 0); for(int i = 1; i <= cnt; ++i) { int u = p[i].u, v = p[i].v; if(sz[v] == a) { dfs2(u, v, 1); dfs2(v, u, 0); for(int j = 1; j <= n; ++j) cout << ans[j] << endl; return 0; } if(sz[v] == b) { dfs2(u, v, 0); dfs2(v, u, 1); for(int j = 1; j <= n; ++j) cout << ans[j] << endl; return 0; } } cout << -1 << endl; return 0; }
  • 阿儿克(arc)
    题意:圆上有3n个不同的点,这些点被染成了n种颜色,每种颜色有3个点,每次可以选择两个颜色相同的点将它们之间(包括它们)用一道弧覆盖,覆盖不能有交叉,两点之间的弧也不能覆盖另一个与它们颜色相同的点,问有多少种方案可以使画出n条弧。(n2e5)
    分析:一眼dp题,圆上不好做,肯定是要考虑破环为链了,但是我们发现就算变成链那也很难,但是我们发现如果有一段被覆盖了,那就可以变成一个不用考虑倒着画弧的链(因为倒着画弧就和之前画的那个弧交叉了),所以我们可以很顺理成章的想到先枚举一条弧的位置,把剩下的弧变成链就可以在链上dp了。一个很显然的想法就是fi,j表示在前i个点有j条弧的方案数,转移方程就是fi,j=fi1,j+(if(c[k]==c[i]))k=1ifk,j,但是显然这个式子是O(n2)的,不能满分,于是我们考虑怎么优化,经过思考可以发现对于k的枚举是很冗余的,所以我们可以尝试预处理出来合法k的位置,再通过记录可以取到数量的最大值来更新方案数,就可以O(n)做出来了。
    总结:看来我不仅dp菜,优化更菜啊,还得多练,这个题简直太妙了(对于我这种蒟蒻来说
    贴个代码
#include <iostream> #include <cstring> using namespace std; const int N = 2e5 + 10, mod = 998244353; int n, tot, ans; int c[N * 3], a[4]; int num[N * 3], mx[N * 3], b[N * 3], last[N * 3], t[N]; int work () { memset(t, 0, sizeof (t)),memset(mx, 0, sizeof (mx)); memset(num, 0, sizeof (num)),memset(last, 0, sizeof (last)); for(int i = 1; i <= tot; ++i) last[i] = t[b[i]], t[b[i]] = i; num[0] = 1, mx[0] = 0; for(int i = 1; i <= tot; ++i) { mx[i] = mx[i - 1], num[i] = num[i - 1]; if(last[i]) { if(mx[last[i] - 1] + 1 == mx[i]) num[i] = (num[i] + num[last[i] - 1]) % mod; if(mx[last[i] - 1] + 1 > mx[i]) mx[i] = mx[last[i] - 1] + 1, num[i] = num[last[i] - 1]; } } if(mx[tot] == n - 1) return num[tot]; return 0; } int main() { cin >> n; for(int i = 1; i <= n * 3; ++i) cin >> c[i]; for(int i = 1; i <= n * 3; ++i) if (c[i] == 1) a[++tot] = i; tot = 0; for(int i = a[2] + 1; i < a[1] + n * 3; ++i) b[++tot] = c[(i - 1) % (n * 3) + 1]; ans = (ans + work()) % mod; tot = 0; for(int i = a[3] + 1; i < a[2] + n * 3; ++i) b[++tot] = c[(i - 1) % (n * 3) + 1]; ans = (ans + work()) % mod; tot = 0; for(int i = a[1] + 1; i < a[3];++ i) b[++tot] = c[(i - 1) % (n * 3) + 1]; ans = (ans + work()) % mod; cout << ans << endl; return 0; }
  • Arpa’s overnight party and Mehrdad’s silent entering
    题意:有n对情侣组成了一个环,可以给每个人赋值0或1,问能不能有一种方案使每对情侣的数字不同,相邻的三个人的数字不能相同。(n1e5)
    分析:乍一看很没思路啊,但是通过思考题解我们可以想到这都是处理两个人之间的问题的,而且还是赋值0或1,所以我们就可以想到用二分图,具体方法就是将情侣之间连边,2i2i1之间连边,然后这就是一个二分图(因为可以证明这个图上都是偶环,而都是偶环的图就是二分图),然后只需要在二分图上跑一边二分图染色就行了。
    总结:没有一点思路,看完题解才恍然大悟,还是太菜。

4|02024-7-10

  • Fortune Wheel
    题意:有一个长度为n的圆,你当前在X,每次你可以有K种走的方案,可以从中选一种往后走,问走到位置0的期望。
    分析:我们发现直接做会很难,但是如果反着做会好很多。设当前走到0的期望是p/q,从0走到前i个的期望是n/i,从前i个走到X的期望是i=11idisi,设disi是s,加起来就会得到s+ni,显然除法可能会失精,所以我们可以跟pq交叉相乘,然后更新就行了。
    总结:类似的题确实没怎么做过,期望感觉确实很难,是旁边的dalao教了我我才会的,希望以后可以多加练习然后能熟练掌握吧。(n1e5,k500)
  • I Might Be Wrong
    题意:你有一个长度为n的01串,每次操作你可以选择里面的一个区间进行排序,设区间里0的个数为cnt0,1的个数为cnt1,每次排序的代价就是|cnt0cnt1|+1,询问把序列修改成0全在前1全在后的最小代价。
    分析:通过手膜样例+自己造一些数据可以发现,这题的所有操作一定满足要操作的区间里的cnt0= cnt1,但是为什么?假设我们要操作的区间里cnt0=cnt1+d,那么不难看出我们可以通过最多d+1次选择cnt0=cnt1的区间就可以把它操作成目标区间,而cnt0=cnt1又肯定是最优的选择,所以我们只需要从第一个出现1的地方往后贪心就行了。有些细节可以自己实现就不讲了。
    总结:这类题都非常的巧妙,乍一看可能觉得会很难,但是只要慢慢推就肯定能推出来简单的做法。
  • Playing Around the Table
    题意:有n个人,n种牌,每种n张,这n个人围城一个圈,每个人有n张牌,每次每个人将自己的一张牌传给下一个人,问一种方案使得能在n(n1)次操作内使i=1nainin100
    分析:直接构造最终状态比较困难,我们考虑一种中间状态,为每个人都有1n牌各一张,不难发现如果我们是这种状态了,那么我们可以用n(n1)2的步数将其变成目标状态,于是我们考虑如何构造方案使我们从初始状态变成中间状态,不难发现对于一个人,如果他有相同的两张牌那就往后传递一张这个牌,如果他的牌正好是1n都有,那就往后传一张前面给他的牌,也不难发现这个的步数是小于等于n(n1)2的,所以我们就可以构造出来一种合法的方案了。
    总结:构造题,思维还是没到位,感觉好难想,但是听完老师讲的之后发现确实很厉害,很巧妙/bx/bx

5|02024-7-11

  • H(n)
    题意:T组数据,对于每组数据给出一个n,求i=1nni(n109T103)
    分析:显然不能直接暴力,考虑怎么优化,发现对于nlnr的值是相等的,所以这一段的计算是冗余的,我们就可以使用整数分块来做,具体来说就是每次循环算出当前值和当前值的两个边界然后直接O(1)乘起来就行了。复杂度O(Tn).
    总结:板子题,较为简单。
  • Bash's Big Day
    题意:找到从1到n的最多的数使得它们的gcd大于一,输出个数。(n105)
    分析:可以用桶存,可以发现选的这些数的gcd肯定是个质数(如果不是质数的话那那个合数也会有质因子),所以我们只需要筛一遍质数,然后找每个质数的倍数桶里有没有就行了。
    总结:简单题,但是刚开始写错了然后被唐氏同桌笑了半天/kk
  • 【模板】有理数取余
    题意:给定a和b,让你求ab(mod 19260817)
    分析:不难发现题目可以转换成bxa(mod 19260817),考虑到模数是质数,我们可以转换xab1abmod2(% 19269817),然后就可以用费马小定理做了。
    总结:正如题意,板子题。
  • [清华集训2012] 模积和
    题意:求 i=1nj=1m(nmodi)×(mmodj),ij (1n,m109)
    分析:考虑到ij很不好做,所以可以把这个式子拆成

i=1n(nni)i=1m(mmi)i=1n(nnii)(mmii)

展开括号,可以得到

(n2i=1nini)(m2i=1mimi)(n2m+i=1nmininimi+i2nimi)

对于前面的所有部分,我们可以发现这是一个整数分块,但是i=1ni2很难算,但是有dalao说可以用拉格朗日插值算(但是其实有更简单的图形法:构造一个n层的正三角形,第i层都是i,然后再把它分别反转120°和240°,然后可以发现每个位置的和都是2n+1,所以值就是n(n+1)(2n+1)6,因为要模数,所以要处理出来6的逆元,然后乘上再模就行了。然后题就做完了。
总结:比较考验推理能力和计算能力,但是总体来说没有什么太难的点,认真做肯定能会。

6|02024-7-12

  • GCD Table
    题意:有一个长度为n的数组a,有n*n的二维数组b,bi,j表示aiaj的gcd,现在将b数组打乱给你,要你求a数组(n500)
    分析:不难想到,原来的b是对称的,也就是说除了i==j的情况,别的数都是偶数,当然也有可能i==j的情况有两个一样,那也是偶数,所以直接输出出现单数次的情况显然是不对的,但是同时我们想到出现的最大的数一定是在a数组的数,所以我们可以考虑将给出的数组从大到小排序,然后判断当前的数是不是之前遍历过的数的gcd(这个可以用一个桶存),如果是那就跳过,如果不是那就证明它也是答案之一,同时将它与前面算出的答案求gcd并更新桶就行了,时间复杂度是O(n3)
    总结:有一定的思维,对我来说有点小难度了已经/qd,但是好好想能想出来。
  • 荒岛野人
    题意:给定n和n组的pi,ci,li,让你求最小的M使

i=1nj=1npi+cixpj+cjx(modM)

分析:可以先把式子化简一下,得到

(pipj)xMy=cjci

如果无解,那M即为所求,那现在做法就显然了,用扩欧求x,若xl[i]l[j]就继续枚举就行了。
总结:确实不太难,推两下就出来了,果然是2002年的题。

7|02024-7-13

没更,原因是:放假+打的ABC里的题没什么好讲的。

8|02024-7-14

  • SuperGCD
    题意:给定两个数a和b,求gcd(a,b) (1a,b101000)
    分析:高精度+辗转相除法gcd即可(也可以用自带高精的Python水过去
    总结:可以练练码力。
  • Tourism
    题意:给n个点和他们的点权和一个数t,共有tn次操作,每次操作从上次的下一个开始,走1~t步,并求出它们的最大值,要求所有操作完了之后到达终点并且每次操作的最大值的和最大。(n106)
    分析:考虑dp,设fi,j表示前i个分了j段的答案,转移方程很简单可以自己推(绝对不是不想打latex了。显然复杂度是平方级别的,所以考虑怎么优化,发现其实第二维可以省略掉,因为最优的解肯定在第i天的时候用的时间是最少的,因为如果你前i个景点不用最少的天数的话,这就意味着你前i天至少还能多去k个景点,这就导致你游览完n个景点的天数不可能是尽量少的,因为你前i个景点用的若干天中可以分担之后一天的工作量,从而少一天时间。所以只用一维就可以了。然后我们发现,对于i,只有fi,ik对后面有贡献,所以我们只需要维护这玩意就行了,一种很显然的思路就是用数据结构做nlogn的复杂度,但是其实可以做到线性,你只需要对于长度为t的每个块维护块中每个点到块两边的前后缀最大值然后需要的时候更新就行了。
    总结:难,真难。
  • 货币系统
    题意:给你n个数,问能用里面最少几个数通过相加表示出这n个数(n100;ai2.5104)
    分析:类似筛法一类的东西,只不过把乘法变成加法了,边筛边用已经能得到的数更新,最后统计答案就行了。
    总结:比较简单,不愧绿题。
  • 毛毛虫
    题意:给定一棵树,可以将树中一条链和其相邻的边抽出来,就组成了一个“毛毛虫”,求这棵树的最大毛毛虫(n3105)
    分析:观察到毛毛虫是由两部分组成,即一条容量最大的从根到叶子的“毛毛虫”和第二大的,因为这两个可以拼成一条。考虑树形dp,记录每个点的出边数量和以它为开始的到叶子的最长链和第二长链,然后在根节点统计就行了。
    总结:比上一题在代码实现方面稍微难了些。直接找树的直径是不对的,显然可以构造一个树包含一个最长链和两个出边非常多的链来heck掉,但是数据比较水,这样的写法居然也能过(

突然写绿题的原因是今天讲课的题太难了,一下午只做出来一道题,崩溃了,遂开始水绿题。

9|02024-7-15

  • Tree with Maximum Cost
    题意:给定一个n节点的数,每个点有点权ai,你需要找到一个点u,使得i=1ndis(i,u)的值最大,输出最大值。(n105)
    分析:不难发现,1号节点为根(u)的时候这个值非常好求,只需要一个dfs就可以,那我们考虑怎么把它转换到其它的点上。首先,我们发现,如果u,v连着一条边,那么如果把u为根转换成v为根就相当于把v的所有子树集体减1(因为离根的距离少了1),不是v的子树的集体+1,那么我们就推出来怎么换根了。实际上,这道题差不多就是换根dp的板题。
    总结:如果没想到换根dp其实后面写的会有点困难,但是写着写着就会发现其实就是换根,思维难度偏简单。
  • [Vani有约会] 雨天的尾巴 /【模板】线段树合并
    题意:给定一颗n节点的树和m次操作,每次在u,v的最短路径上放一个数字z,最后问你每个点上哪个数字出现次数最多。(n,m,z105)
    分析:首先要求每个值出现的次数,那我们不妨先在每个点建立一颗动态开点的权值线段树来储存最大值,然后发现要区间加,那肯定要用到差分的思想,但是发现每个点的权值合并很慢,每次需要O(n),就算用启发式合并也会比较慢,所以我们考虑一个新做法:线段树合并,它可以让每次合并的时间优化到O(logn)级别。具体就是如果两颗线段树,如果a线段树的左儿子和b的左儿子都有值,那就进去继续递归,如果a的没有,那就直接让a的那部分指向b,反之一样,这样就可以做到很快的合并了。代码实现有些困难,有些坑。
    总结:考试题用到了这个东西,结果发现自己还没学过,所以赶紧补一下。

10|02024-7-16

  • OKR-Periods of Words
    题意:定义字符串Qa的周期当且仅当aS的真前缀且a+a(前后缀相同的地方可以合并)也是Q的前缀,现在给你一个长度为n的字符串S,要你对于它求它的所有前缀的最大周期长度之和
    分析:不难发现,这个题跟KMP关系很大,因为对于每一个前缀的最大周期,它的结尾一直跳nxt数组直到不能能跳为止之后的下标就是它的最大周期的剩余部分,那么对于每一个前缀,它的最大周期就是其长度减去跳到不能跳为止的nxt数组的下标,我们可以记忆化地更新下标来优化时间复杂度,就可以过了。(n106)
    总结:看出来跟KMP有关系之后就很好做了。
  • AC 自动机(简单版)
    (三道模板题放到一起讲了)
    题意:给定n个模式串𝑠i和一个文本串𝑡,求有多少个不同的模式串在文本串里出现过(n105)
    分析:如果单独问两个字符串的出现关系的话,我们可以用KMP,但是有n个,的时候就很难受,会很慢,所以考虑如何优化。我们可以建立一个trie树,然后在上面跑类似于KMP的东西,就可以很快了,基本上是线性。(没错我不太想具体写了,有时间专门开一个博客写这玩意吧)
    分析:失配指针其实并没有所说的那么难理解,画画图就会了。
  • L 语言
    题意:给你n个字符串和m个文本串,你可以改变这n个字符串的组合方式使它们与每个文本串的lcp最长(n,m50,s.len,t.len2106)
    分析:既然要匹配相同的前缀,还要多次询问,那我们很容易就能想到AC自动机,事实上这道题也相对比较板,如果fi可以被理解且i~j在AC自动机上出现过且没被用那么我们很显然就可以跳过去然后更新fj,只是如果不加优化的话过不去,但是优化也比较简单,就判断一下如果已经可以被理解就不用再往后走了,就可以愉快AC了。
    总结:事实上我对于AC自动机的掌握和理解其实还没有那么全面,这种题我还看了题解,可能是因为做的题还不够多吧。

11|02024-7-17

  • 单词
    题意:给你n个单词组成的文章,问你文章里的每个单词分别在文章里出现了多少次(每个单词之间有空格)所有字符串的长度(106)
    分析:还是一道AC自动机的变式,因为单词之间有空格,所以我们就可以考虑把每个单词后面加一个#然后加入AC自动机,然后再分别查询就行了。
    总结:本来以为暴力可以水过去,结果它又加了个subtask,把暴力卡了,所以被迫打正解。
  • 卡农
    题意:求在{1~n}的非空子集中选m个不同子集的方案数,使每个元素都被选择n次,答案对108+7取模(1n,m106)
    分析:计数,显然考虑dp。设fi表示有序选择i个子集的满足的方案数,那么答案就是fmm!,而出现的次数奇偶有限制,所以第i个会被前i1个确定下来,也就是只有它与前面i1个数的亦或为0的时候才合法,现在我们如果不考虑不相同条件,非空的限制,总方案数就是(2n1)!(2ni)!,现在我们考虑容斥掉限制,如果第i个子集为空集,则前i1个子集合法,所以要减去fi1,再考虑相同的情况,如果i与前面某个数相同,那么删去两个相同的子集之后就合法了,方案数就是减去两个数的方案数乘上i1再乘上所有数(子集)选出来i1个数(子集)的方案数,也就是要减掉(i1)(2n(i2)1)fi2,然后我们发现这些东西差不多都可以预处理出来,所以我们就可以做到线性做了。
    总结:不知道怎么回事,可能是脑袋突然开窍了,上课居然听懂这道题了,下课赶紧做完了,感觉确实妙。
  • Balanced Subsequences
    题意:给定n,m,k,求有多少个由n(m)组成的序列满足最长的合法括号子序列的长度恰为2k
    分析:首先如果k<min(n,m)考虑维护一条折线,从(0,0)到(n+m,n-m),最低点是k-m,但是学长说这玩意不好维护,所以记f(t)是从(0,0)(n+m,nm)且最低点不大于tm的数量,答案就是f(k)=f(k1),然后考虑卡特兰数,把折线最后一次触碰直线y=tm交点之后的部分上下翻转,那就成了一条从(0,0)到(n+m,2tnm)的折线,方案数就是(n+mk)
    总结:最神仙的一题,很多转换都没见过,感觉很新,很妙,果然是计数题。

12|02024-7-18

快写完的时候电脑关机了,只能再写一遍了。

  • The Bakery
    题意:将一个长度为n的序列分成k段,每段的价值是该段中不同的数字的个数,问最大价值是多少(n35000,k50)
    分析:显然dp,设fi,j表示前i个分了j段的最大价值,那转移方程就是fi,j=kfk,j1+val(k+1,i)val(i,j)表示i~j不同的数个数),但是我们可以发现这个方程是O(n2k)的,考虑优化。发现val()里的值永远不变可以考虑用线段树维护查询和修改操作,然后就做完了。
    总结:可以很有效的加深对线段树等数据结构的理解
  • Fox and Card Game
    题意:桌子上有n堆牌。每张牌上都有一个正整数。A可以从任何非空牌堆的顶部取出一张牌,B可以从任何非空牌堆的底部取出一张牌。A先取,当所有的牌堆都变空时游戏结束。他们都想最大化他所拿牌的分数(即每张牌上正整数的和)。问他们所拿牌的分数分别是多少?(n100)
    分析:每个人肯定都想要大的分数的牌,如果这张牌在小A这边(在某一堆中位置更靠近上部),小A显然不会放弃这张牌,即她看到小B取这堆的堆底时,也可取堆顶,防止小B那道那张牌,所以谁都想那相对大的牌,那最后也都会拿离自己最近的。考虑一堆的牌数双数的时候我们可以直接分成两半分,但是如果是单数的话我们就需要特殊判断一下了,因为每个人都想选大的,如果全是单数堆了那先手肯定选中间最大的堆这样自己就能选到尽量大的了,所以我们直接加一个大根堆对牌数是单数的堆做一下就行了。
    总结:比较考验归纳整理。
  • Choosing Carrot
    题意:一排n个数,甲乙轮流取,甲先取,每次取的时候都只能从两端取,直到只剩一个数为止。甲希望剩下的最大,乙希望剩下的最小。在正式开始之前甲还可以按照同样的规定先取k个数,问对于每个k剩下的都会是多少。
    分析:先不考虑k,可以发现每个人都考虑最优的话最后剩下的就是中间的那几个,很好做,那选k个怎么办,发现不好想,因为有很多拿的方式,可能会改变mid也可能不改变,所以我们就用dp跑一边就行了。
    总结:这道题相对难度较小,但是还是很考察综合能力
  • Mex Mat
    题意:给定n阶网格A的第一行第一列,矩阵中剩下的数Ai,j=mex(Ai1,j,Ai,j1),问A矩阵中有多少0,1,2(n5105)
    分析:规律很难找,那就先打表。打表不难发现每44的矩阵会不断重复循环,所以我们只需要处理这44的矩阵就行了,然后直接按倍数乘过去。
    总结:拜谢打表找规律!

13|02024-7-21

  • 写了网络流板子题,具体看这个
  • 负载平衡问题
    题意:一个有n个仓库围成的圆,每个仓库ai个货物,每次操作可以将相邻两个仓库中的一个移到另一个仓库,问最小操作次数使每个仓库中货物件数相同
    分析:显然贪心。不难发现必定至少有两个相邻的人不需要从别人那里获得纸牌,所以我们直接破环成链,枚举相邻的不需要交换纸牌的两人,然后显然当我们取得减数是中位数的时候答案最小,就做完了
    总结:本来是搜网络流来着,结果直接贪心过了也是逆天

14|02024-7-22

今天是考试。

  • 难(nan)
    题意:给定两个字符串 a, b,从 a 中选一段前缀,b 中选一段后缀(前后缀都可以为空),并将选出的后缀拼在选出的前缀后面。你需要求出有多少种本质不同的串(可以为空)。(lena,lenb106)
    分析:发现很难正着做,因为如果正着做的话基本上全都是不知道前面有没有出现过的方案,所以必须用hush存,只能过40pts,所以考虑正难则反。考虑总方案减去重复方案,现在考虑怎么找到非法方案。考虑到最长合法方案肯定是两个串拼起来,而能删掉的恰好是从此交界处向左右无限制删,不难发现,若两串拼起来的地方的左右有相同的,那肯定会有一个非法串,所以答案就很显而易见了,直接找b串中每个字母出现次数和a串中的出现次数,相同字母乘起来就是非法的了,总数也显然就是(lena+1)×(lenb+1)
    总结:有些思维量,放T1挺合适的,当时写完之后一直没过大样例,挑了20min才发现是longlong没开。。
    贴个代码
#include <iostream> #include <cstring> #include <cstdio> using namespace std; typedef long long ll; const int N = 1e6 + 10; char a[N], b[N]; ll lena, lenb; ll s[N]; ll ans; int main() { // freopen("nan.in", "r", stdin); // freopen("nan.out", "w", stdout); cin >> (a + 1) >> (b + 1); lena = strlen(a + 1), lenb = strlen(b + 1); ans = (lena + 1) * (lenb + 1); for(int i = 1; i <= lenb; ++i) s[b[i] - 'a']++; for(int i = 1; i <= lena; ++i) ans -= s[a[i] - 'a']; cout << ans << endl; return 0; }
  • 砝码(weight)
    题意:有n个砝码,每个砝码都有初始重量aiQ次操作,每次操作有下列两种:
    1:l,r,x:表示把lr的所有ai变成x
    2:l,r,x:查询lr的所有砝码,每个砝码可以用无数次,是否能称出重量x
    ai和所有的x都不大于m
    保证ai和所有操作 1 的x总共最多不超过 10 种数字。(n,Q106,m105)
    分析:发现区间修改,区间查询,加上这个范围,肯定是线段树维护区间内出现哪些值了。但是我们发现这种维护并不简单,而且每次还要做完全背包,时间肯定会炸,所以想办法优化。发现不同的值最多有10个,显然状压啊,于是直接开始状压每个出现的数,然后预处理出来每个数的组合的完全背包,然后直接线段树修改和合并就行了。
    总结:比较好想,但是有点难实现,考场上就是没实现出来,挂大分,直接死掉,但是下午调出来了/hanx
    贴个代码
#include <iostream> using namespace std; const int N = 1e6 + 10; int n, m, q, tot; int a[N], vis[N], v[N], p[N]; bool f[1100][100001]; struct zx{int l, r, v, tag;} t[N * 4]; struct rx{int op, l, r, x;} e[N]; void pushup(int x) { t[x].v = t[x << 1].v | t[x << 1 | 1].v; } void pushdown(int x) { if(t[x].tag) { t[x << 1].v = t[x << 1].tag = t[x].tag; t[x << 1 | 1].v = t[x << 1 | 1].tag = t[x].tag; t[x].tag = 0; } } void build(int x, int l, int r) { t[x].l = l, t[x].r = r, t[x].v = t[x].tag = 0; if(l == r) { t[x].v = 1 << (vis[a[l]] - 1); return; } int mid = (l + r) >> 1; build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r); pushup(x); } void add(int x, int L, int R, int k) { int l = t[x].l, r = t[x].r; if(l >= L && r <= R) { t[x].v = t[x].tag = 1 << (k - 1); return; } pushdown(x); int mid = (l + r) >> 1; if(L <= mid) add(x << 1, L, R, k); if(R >= mid + 1) add(x << 1 | 1, L, R, k); pushup(x); } int query(int x, int L, int R) { int l = t[x].l, r = t[x].r; if(l >= L && r <= R) return t[x].v; pushdown(x); int mid = (l + r) >> 1, res = 0; if(L <= mid) res |= query(x << 1, L, R); if(R >= mid + 1) res |= query(x << 1 | 1, L, R); return res; } int main() { // freopen("weight.in", "r", stdin); // freopen("weight.out", "w", stdout); cin >> n >> m >> q; for(int i = 1; i <= n; ++i) cin >> a[i]; for(int i = 1; i <= n; ++i) if(!vis[a[i]]) v[++tot] = a[i], vis[a[i]] = tot; for(int i = 1; i <= q; ++i) { cin >> e[i].op >> e[i].l >> e[i].r >> e[i].x; if(e[i].l > e[i].r) swap(e[i].l, e[i].r); if(!vis[e[i].x] && e[i].op == 1) v[++tot] = e[i].x, vis[e[i].x] = tot; } build(1, 1, n); for(int i = 0; i < (1 << tot); ++i) { int cnt = 0; for(int j = 1; j <= tot; ++j) if(i >> (j - 1) & 1) p[++cnt] = v[j]; f[i][0] = 1; for(int j = 1; j <= m; ++j) for(int k = 1; k <= cnt; ++k) if(j >= p[k]) f[i][j] |= f[i][j - p[k]]; } for(int i = 1; i <= n; ++i) { if(e[i].op == 1) add(1, e[i].l, e[i].r, vis[e[i].x]); else cout << (f[query(1, e[i].l, e[i].r)][e[i].x] ? "Yes" : "No") << endl; } return 0; }
  • 交易(trade)
    题意:小L是一个商人,特长是靠交易物品从中获利。通过某种神秘手段,小L得 知了未来n天某种物品在市场上的价格,小L每天可以执行以下三种操作(假设 当天这种物品的市场价为x金币):
    1.用x金币买入一个物品(注意不能买多个)。
    2.以x金币的价格卖出一个物品(注意不能卖多个且之前必须有至少一个物品)。
    3.什么也不做。
    假设小L一开始有无限多个金币且没有该种物品,且他一定会使用让自己赚 钱最多的策略交易物品。现在小Q想知道小L最多能赚多少个金币。由于小L不想告诉小Q每天的价格,只告诉他每天的价格只有可能是1金币或者2金币。现在他想知道有多少种可能的情况,使得小L最多能赚到k个金币。
    分析:原题,Balanced Subsequences改改就能过,可以直接看我前面的这道题的讲解。
    总结:虽然是原题,但是考场上我推错式子了,直接爆弹。
    贴个代码
#include <iostream> #include <cstdio> using namespace std; typedef long long ll; const int N = 1e6 + 10, p = 998244353; int tp, n, k; ll inv[N], jc[N]; void work() { jc[1] = jc[0] = inv[1] = inv[0] = 1; for (int i = 2; i <= 1e6; ++i) inv[i] = (ll)(p - p / i) * inv[p % i] % p; for (int i = 2; i <= 1e6; ++i) jc[i] = (jc[i - 1] * i) % p; for (int i = 2; i <= 1e6; ++i) inv[i] = inv[i] * inv[i - 1] % p; } ll work2(ll n, ll k) { return ((n + 1 - 2 * k) * ((jc[n] * inv[k] % p * inv[n - k] % p) % p - (jc[n] * inv[k - 1] % p * inv[n - k + 1] % p + p) + p) % p + p) % p; } int main() { // freopen("trade.in", "r", stdin); // freopen("trade.out", "w", stdout); cin >> tp >> n; work(); if(tp == 1) { cin >> k; cout << work2(n, k) % p << endl; } else { ll ans = work2(n, 0) % p, num = 1; for(int i = 1; i <= n / 2; ++i) num = num * 233ll % p, ans = (ans + (num * work2(n, 1ll * i)) % p + p) % p; cout << ans << endl; } return 0; }

15|02024-7-23

  • 克莱因瓶(klein)
    题意:给你一张网格图,每格可以往上或左走一格,(i,0)(i,n)可以花1的时间穿越,(0,j)(n,nj)可以花1的时间穿越,现在有q次询问,每次给出两个点,问你两个点相遇的最小花费(n1018,q105)
    分析:很容易想到让一个点不动,另一个点去找那个点,这样肯定不劣,然后就会想到对于上下穿越和左右穿越肯定每个最多只会进行一次,因为如果再次进行还不如直接不进行,这样我们就有了一个做法,对于每次询问,去暴力往四个方向走到边界并穿越,考虑到最多进行两次,所以只需要连续做两次,然后直接把所有情况的花费取最小就行了,注意在穿越0次和1次的情况也要考虑到。
    总结:符合NOIpT1的难度,但是我个唐氏花了一个小时。
    贴个代码
#include <iostream> #include <cstring> #include <cmath> using namespace std; typedef long long ll; const ll N = 1e4 + 10, inf = 1e18; ll n, q, ans; ll x1, x2, y11, y2; void dfs(ll x, ll y, ll st, ll now) { ans = min(ans, st + abs(x - x1) + abs(y - y11)); if(now == 2) return ; dfs(n, n - y, st + x + 1, now + 1); //(0,j)(n,n-j) dfs(0, n - y, st + n - x + 1, now + 1); //(n,n-j)(0,j) dfs(x, n, st + y + 1, now + 1); //(i,0)(i,n) dfs(x, 0, st + n - y + 1, now + 1); //(i,n)(i,0) } int main() { // freopen("klein.in", "r", stdin); // freopen("klein.out", "w", stdout); scanf("%lld%lld", &n, &q); while(q--) { scanf("%lld%lld%lld%lld", &x1, &y11, &x2, &y2); ans = inf; dfs(x2, y2, 0, 0); printf("%lld\n", ans); } return 0; }
  • Multiset Mean
    题意:你有1 ~ n的数,对于里面每个数你需要用1 ~ n中的数最少0次最多k次,使它们的平均数是那个数,问对于每个数的方案取模给定质数的值。(n,k100)
    分析:一个巧妙的转化:设当前需要成为的平均数是x,那就把1 ~ n全部减x,然后只需要做多重背包使和为0就行了,但是发现时间复杂度比较危险,所以考虑优化,发现每次背包的时候都加的是前缀和,所以考虑前缀和维护一下,就很安全了。
    总结:没想到转化,考场上直接寄了,希望以后能记住。
    贴个代码
#include <iostream> using namespace std; typedef long long ll; const int N = 101, M = 505000; ll n, m, p, tot; ll f[N][M], s[M]; int main() { // freopen("multiset.in", "r", stdin); // freopen("multiset.out", "w", stdout); cin >> n >> m >> p; f[0][0] = 1; for(int i = 1; i <= n; ++i) { tot += i * m; for(int j = 0; j <= tot; ++j) { s[j] = f[i - 1][j]; if(j >= i) s[j] = (s[j] + s[j - i]) % p; f[i][j] = s[j]; if(j >= i * (m + 1)) f[i][j] = (f[i][j] - s[j - i * (m + 1)] + p) % p; } } for(int i = 1; i <= n; ++i) { ll ans = 0; for(int j = 0; j <= tot; ++j) ans = (ans + ((f[i - 1][j] * f[n - i][j]) % p) % p) % p; ans = (ans * ((m + 1ll) % p) % p) % p; ans = (ans - 1 + p) % p; cout << ans << endl; } return 0; }
  • Count Permutations Many Times
    题意:给定长度为n的数列,有q个询问,每个询问给lr,要你构造长度为n1 ~ n每个值只出现一次的数列P,使对于(lj<r)pjaj,问方案数对998244353取模之后的结果(n,q2000)
    分析:如果给定的数列是一个排序,那很简单,只需要钦定fi,j表示前i个至少j个pk=ak,转移很简单,然后直接容斥就行了。那如果数列不是排序呢,我们可以发现我们可以把每个ai相等的点放到一起,这样也差不多和排列一样,只需要转移的时候乘一下当前数字的个数就行了。
    总结:比较难想,反正考场没想出来,感觉挺妙的,以后做题要从特殊性质入手慢慢推到正解。
    贴个代码
#include <iostream> using namespace std; typedef long long ll; const int N = 2e3 + 10, p = 998244353; ll n, q, tot, ans; ll f[N][N], g[N][N], jc[N], num[N], a[N], s[N]; int main() { // freopen("perm.in", "r", stdin); // freopen("perm.out", "w", stdout); cin >> n >> q; for(int i = 0; i < n; ++i) cin >> a[i]; g[0][0] = jc[0] = 1; for(int i = 1; i <= n; ++i) jc[i] = (jc[i - 1] * i) % p; for(int i = 1; i <= n; ++i) for(int j = 0; j <= i; ++j) g[i][j] = (g[i - 1][j] + (j ? g[i - 1][j - 1] : 0)) % p; ll l, r; while(q--) { cin >> l >> r; for(int i = 0; i < n; ++i) num[i] = 0; ans = tot = 0; for(int i = l; i < r; ++i) ++num[a[i]]; for(int i = 0; i < n; ++i) if(num[i] == 1) s[++tot] = 1; int st = tot + 1; for(int i = 0; i < n; ++i) if(num[i] > 1) s[++tot] = num[i]; for(int j = 0; j < st; ++j) f[st - 1][j] = g[st - 1][j]; for(int i = st; i <= tot; ++i) for(int j = 0; j <= i; ++j) f[i][j] = (f[i - 1][j] + (j ? f[i - 1][j - 1] * s[i] : 0)) % p; ans = jc[n]; for(int i = 1; i <= tot; ++i) ans = (ans + (i & 1 ? p - 1 : 1) * f[tot][i] % p * jc[n - i] % p + p) % p; cout << ans << endl; } return 0; }

16|02024-7-24

  • 小B的询问
    题意:给定一个长度为n阈值为[1~k]的区间,有m次询问,每次给出l,r,问你i=1kci2,其中ci表示i[l~r]区间内的出现次数(n,m,k5×104)
    分析:显然可以发现如果区间加一,那么多的那个数的贡献就是2cnti+1,其中cnti表示i在区间不加一之前的个数。接下来我们想怎么转移,看完题之后会发现这道题是没有修改,只有查询,这不显然的莫队吗,然后就发现实现起来也就没什么困难之处了,就过了。
    总结:板子题啊没啥好说的
  • Count on a tree II
    题意:给定n个节点的树,有m次询问,每次给(u,v),问(u~v)的路径上有多少不同的颜色(n4×104,m105)
    分析:还是没有修改只有查询,考虑树上带修莫队,如何维护路径呢,dfs序肯定不行,但是欧拉序可以!如果两点不是祖先关系,那两点间的欧拉路径中值出现一次的点+两点的lca就是它们的路径了,如果是祖先关系的话那就特判一下就行了。难点在实现吧
    总结:板子题,但是有难度
  • 王室联邦
    题意:国家有N个城市,编号为 1N。 一些城市之间有道路相连,任意两个不同的城市之间有且仅有一条直接或间接的道路。 为了防止管理太过分散,每个省至少要有 B 个城市。 为了能有效的管理,每个省最多只有 3×B 个城市。
    每个省必须有一个省会,这个省会可以位于省内,也可以在该省外。 但是该省的任意一个城市到达省会所经过的道路上的城市(除了最后一个城市,即该省省会)都必须属于该省
    一个城市可以作为多个省的省会。
    分析:树上问题,dfs+最后特判一下有没有父亲节点没有属于的就行了
    总结:不该蓝题

17|02024-7-25

  • Grass Planting G
    题意:给定一个n节点的树,有m次操作P/Quv,若为P则让你将uv的路径加一,反之则为查那两个点的边的值,保证这两个点直接相连。(n,m105)
    分析:看起来像树剖,但是树剖是点权,所以考虑怎么转化,考虑到每个儿子节点上面的边都只有一个,所以考虑把边权转化为与它相连的儿子点,但是如果两个点之间的点的点权加一的话它们的lca是不加的,所以减掉就行了。
    总结:需要打比较长时间
  • 旅游
    题意:给定一棵n节点的树,有q次操作,每次给(u,v),询问(u~v) 路径中两点点权差最大(且要先经过较小值)并将路径经过的点的点权+v (n,q5×104)
    分析:发现肯定用树剖+线段树,想到要小的在大的前面,所以不能只维护最小和最大值,又想到可以维护从左边开始走的最大答案和从右边的,发现维护完之后就可以直接做了。
    总结:码量惊人,非常难调

18|02024-8-4

  • BLO-Blockade
    题意:给你一个n节点m边的连通图,对于每个点,询问当这个点被删除之后不能连通的点对的数量(正反算不同)(n5×105,m2×106)
    分析:考虑如果这个点不是割点,那就会有2×(n1)个点对,如果是割点,那删除这个点之后必然整个图会被分成很多份,而每两份之间都会有贡献,所以我们只需要tarjan的时候把删去点之后每份贡献的答案一次算出来然后累加起来就行了。
    总结:差不多版子题,但是贡献那里有些思维量。

__EOF__

本文作者Rose_Lu
本文链接https://www.cnblogs.com/roselu/p/18288951.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Rose_Lu  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示