ICPC昆明区域赛·赛前挣扎复习题
HihoCoder - 1629 Graph (回滚莫队+并查集按秩合并)
题目:https://vjudge.net/contest/419697#problem/C
遇到了回滚莫队。
https://blog.csdn.net/a54665sdgf/article/details/82501086
开拓了眼界。
并查集随机删除是不行的,但是可以按顺序撤销,回滚莫队恰好可以支持这种操作。
[hihocoder 1635] Colored Nodes(思维+基环树)
https://blog.csdn.net/alan_cty/article/details/78713865
发现,虽然一轮之后,每个点的颜色不知道,但是知道这个点的颜色是从哪个点继承的
然后继承关系是一个基环树
只有环上的颜色最后F[i]值不为0
且环上每个颜色最后答案一样。
然后做一下就行
关键在于发现:颜色不知道,但是知道这个点的颜色是从哪个点继承的,然后转化为基环树问题。
D - Rikka with Subsequences
https://blog.csdn.net/wxh010910/article/details/84950709
(全网仅有的wxh的题解。。。。)
cnt^3怎么处理?
套路:用组合意义拆开。
问题转化成为,三个相同的串,从每个串中分别选出一个子序列,这三个子序列相同的方案数。(方案不一样,当且仅当存在从某个串中选出来的子序列的位置不一样)
但是还是很不好写。。。因为涉及到邻接矩阵的限制的问题。
(此处省略理解代码1.5h)
统计i,j,k位置结尾的合法子序列,必须要考虑上一个是不是能接上去,但是复杂度太高不能再枚举了。
所以先固定i,就知道了a[i],也就是b[j],c[k]必须等于a[i],否则没有意义。
很自然地设sum[j][k]表示,所有a中选<i的,b中选<j的,c中选<k的,且能直接拼在a[i]后面的三个位置作为结尾的合法子序列的方案数(显然这三个位置的字符相同)。
到了一个新的i,那么sum就必须全部重新维护(因为a[i]变了),
而sum[j][k]必然包含了从第二个串选1,2,...j-1的,第三个串选1,2,...,k-1位置的,按照二维前缀和的思想,sum[j][k]=sum[j-1][k]+sum[j][k-1]-sum[j-1][k-1]+val
考虑val是什么
就是以所有(<i,j-1,k-1)结尾的且能拼到i后面的子序列的方案数(要保证三个位置字符相同)
设这个东西为dp[j][k](i维省了),那么dp[j][k]可以在之前几轮i的时候,枚举到a[i]==b[j]==c[k]的(i,j,k)的时候更新到
只要我们先更新sum,再求i层的贡献,再更新dp[j][k]就行了。
代码(from:wxh)
#include <bits/stdc++.h> using namespace std; const int N = 234; const int md = 1e9 + 7; int n, a[N], dp[N][N], sum[N][N]; char board[N][N]; inline void add(int &x, int y) { x += y; if (x >= md) { x -= md; } } inline void sub(int &x, int y) { x -= y; if (x < 0) { x += md; } } int main() { int tt; scanf("%d", &tt); while (tt--) { scanf("%d", &n); for (int i = 0; i < n; ++i) { scanf("%d", &a[i]); --a[i]; } for (int i = 0; i < n; ++i) { scanf("%s", board[i]); } for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { dp[i][j] = 0; } } int answer = 0; for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { for (int k = 0; k < n; ++k) { sum[j + 1][k + 1] = board[a[j]][a[i]] == '1' ? dp[j][k] : 0; add(sum[j + 1][k + 1], sum[j + 1][k]); add(sum[j + 1][k + 1], sum[j][k + 1]); sub(sum[j + 1][k + 1], sum[j][k]); } } for (int j = 0; j < n; ++j) { for (int k = 0; k < n; ++k) { if (a[i] == a[j] && a[i] == a[k]) { int ways = 1; add(ways, sum[j][k]); add(answer, ways); add(dp[j][k], ways); } } } } printf("%d\n", answer); } return 0; }
总结:
1.组合意义拆分次方
2.注意固定i以后,得到更多的条件以简化复杂度,比如这里sum就额外赋予了新的要求:子序列能接在i的后面。这个sum用平行的循环更新就行。
3.思路:组合意义转化->sum设出来->再维护dp->处理好先后顺序
C - Vasya and Robot
https://vjudge.net/contest/429333#problem/C
思路别想太复杂=。=
考虑选出一个区间行不行,发现只要考虑区间外面U,D,L,R个数就行了
然后发现可以二分。没了。
(其实有单调性可以只扫一遍,固定右端点,左端点往左走,直到成为合法区间,再往左移动右端点,左端点也不用回撤)
D - Berland Fair
https://vjudge.net/contest/429333#problem/D
可以直接用线段树模拟。
注意,pushdown的标记也要下放给子区间!!!
pym的方法:
重复:
1.能完整绕圈就绕圈。(取模)
2.否则,从1出发暴力走一圈,模拟这一圈的过程。绕完以后,删除a[i]>T的i(用个堆删),维护剩下a[i]的和(便于1操作绕圈)
直到T变为0或者删完
复杂度:
每次绕圈会让T至少减半。
否则如果不能绕圈,那么暴力走一遍,如果走了一遍没有使T减半,那么意味着真正做出贡献的a[i]的和(设为S)很小,而其他没有做出贡献的a[i]就再也不能走了。
这样的话,S<<T,那么下次一定可以绕圈。所以还是能让T减半
很好写。
算是一种绕圈取模题的思路,考虑让规模减半,使得暴力次数不多。