国庆摸底测试
考了上下午两场六题,应该是模拟noip的。
第三题过于难,考的奇葩数论。T5T6都简单了些。
得分100 + 100 + 40 + 100 + 0 + 30 = 370
简单分析一下,主要失误在于T5。
三个100就不说,T3尽力了。T6是类似魔法森林的一道题,是个删边维护生成树的套路,没想出来。
T5显然在我的能力范围之内,爆0。
大意:一个寂寞堆的性质是:子节点不大于父节点 && 左子树每个点都不大于柚子树。
给你一个满二叉树,问至少改变几个数能使它变成寂寞的。
当时我满脑子都是树形DP,区间DP,但是怎么想都不对...
然后发现可以把每个点之间的关系做成图,不满足的条件作为边,然后跑最小点覆盖。
然后发现这TM不是二分图啊......
然后又去想怎么在这个二叉树上面DP,没搞出来......
然后准备把小数据特判得30分,但是因为我愚蠢的输出了树中节点个数导致这30分也凉了。
正解:注意上面,把每个点的关系做成图。
实际上这个图的拓扑序唯一,因为任意两个点的大小都可以藉由lca得出。
所以按照逆拓扑序求最长不升子序列就OK了。
拓扑序在这里就是中右左序。
接着讲一下T6的套路:题意:
给你n个点,q次操作,有加边,删边,查询连通性。
且每两点的边至多只会被加/删一次。
n<=400,q<=100000
解:
考虑暴力,显然是每次DFS。这样做是qm的,m上限有n²,所以是qn²,能拿30分。
然后我们发现这个n很小,有什么用呢?不知道。
然后我们考虑转图为树,那么只需维护生成树即可。
维护哪些生成树呢?删边时间最靠后的!
因为如果走一些删边时间靠前的边能到达,那么这个生成树也一定可达。
如果这个都不可达,那么删边时间靠前的更不可达。
然后,遍历树是n的,于是时间被压到了qn,4e7可以过。
删边的时候如果还在就删。加边的时候,如果连通就去掉删除时间最早的。查询直接DFS。
实际操作上,由于是森林所以不需要vis数组。记录父亲即可。
用vector代替邻接表存边,以节省遍历被删的边的时间,做到O(n)。

1 #include <cstdio> 2 #include <vector> 3 #include <algorithm> 4 #include <cstring> 5 6 const int N = 405, INF = 0x3f3f3f3f, M = 100010; 7 8 std::vector<int> G[N]; 9 char c[M][10]; 10 int del[N][N], da, db, xx[M], yy[M]; 11 12 bool DFS(int x, int t, int f) { 13 //printf("DFS : x = %d \n", x); 14 if(x == t) { 15 return 1; 16 } 17 for(int i = 0; i < G[x].size(); i++) { 18 //printf("G[%d][%d] = %d \n", x, i, G[x][i]); 19 int y = G[x][i]; 20 if(y != f && DFS(y, t, x)) { 21 return 1; 22 } 23 } 24 return 0; 25 } 26 27 bool getmin(int x, int T, int f) { 28 if(x == T) { 29 return 1; 30 } 31 for(int i = 0; i < G[x].size(); i++) { 32 int y = G[x][i]; 33 if(y == f) { 34 continue; 35 } 36 int t = getmin(y, T, x); 37 if(t && (del[x][y] <= del[da][db])) { // error : < 38 da = x; 39 db = y; 40 } 41 if(t) { 42 return 1; 43 } 44 } 45 return 0; 46 } 47 48 int main() { 49 freopen("fool3.in", "r", stdin); 50 freopen("my.out", "w", stdout); 51 int n, m; 52 scanf("%d%d", &n, &m); 53 int x, y; 54 memset(del, 0x3f, sizeof(del)); 55 for(int i = 1; i <= m; i++) { 56 scanf("%s", c[i]); 57 scanf("%d%d", &xx[i], &yy[i]); 58 if(c[i][0] == 'd') { 59 del[xx[i]][yy[i]] = del[yy[i]][xx[i]] = i; 60 } 61 } 62 63 for(int i = 1; i <= m; i++) { 64 x = xx[i]; 65 y = yy[i]; 66 if(c[i][0] == 'd') { // del 67 for(int i = 0; i < G[x].size(); i++) { 68 if(G[x][i] == y) { 69 std::swap(G[x][i], G[x][G[x].size() - 1]); 70 G[x].pop_back(); 71 break; 72 } 73 } 74 for(int i = 0; i < G[y].size(); i++) { 75 if(G[y][i] == x) { 76 std::swap(G[y][i], G[y][G[y].size() - 1]); 77 G[y].pop_back(); 78 break; 79 } 80 } 81 } 82 else if(c[i][1] == 'd') { // add 83 getmin(x, y, 0); 84 if(del[x][y] <= del[da][db]) { 85 if(da) { 86 da = db = 0; 87 continue; 88 } 89 G[x].push_back(y); 90 G[y].push_back(x); 91 continue; 92 } 93 for(int i = 0; i < G[da].size(); i++) { 94 if(G[da][i] == db) { 95 std::swap(G[da][i], G[da][G[da].size() - 1]); 96 G[da].pop_back(); 97 break; 98 } 99 } 100 for(int i = 0; i < G[db].size(); i++) { 101 if(G[db][i] == da) { 102 std::swap(G[db][i], G[db][G[db].size() - 1]); 103 G[db].pop_back(); 104 break; 105 } 106 } 107 da = db = 0; 108 G[x].push_back(y); 109 G[y].push_back(x); 110 } 111 else { // ask 112 printf("%d\n", DFS(x, y, 0)); 113 } 114 } 115 116 return 0; 117 }
时间卡的很紧,0.9s过的大数据。可以用LCT优化到qlog²n,那就是魔法森林了。
总结:初级的题没有失误很好;T3,T5这种难度的尽量争取满分,没把握就面向数据,反正部分分一定要拿稳。如果时间还多,暴力又打完了,检查一万遍了,尽量爆肝正解。说不定灵光一闪就A了;不要太自信,对拍是必要的。没有对拍就手造10min的数据。
[update]:T3简直天秀,真·神奇解法,天下第一。
之后填坑。