zoukankan      html  css  js  c++  java
  • CSP-S2019-day1

    pts:195

    T1: 95

    T2:100

    T3: 0

    T1

    [CSP-S2019] 格雷码

    数论,递归

    solution

    找规律,不难发现每位之间格雷码都有一定的关系,例如: 4 位格雷码的每一个数把相比于 3 位格雷码加上的部分再去掉,剩下的都能在 (3) 位格雷码中找到,那么输出某一个格雷码时,可以不停的向更低位寻找它所在的位置,如果在前一半就输出 1,否则输出 0

    代码

    T2

    [CSP-S2019] 括号树

    树形结构,树的应用

    solution

    状态:

    • (f[i]) 表示根节点到 (i) 号点合法括号的方案数

    • (last[i]) 根节点到 (i) 号点距离 (i) 最近还没有匹配的左括号的位置

    • (tot[i]) 表示 (i) 号点到根节点连续匹配的括号个数

    注意:

    ((())) 没有连续匹配的括号,所以 (tot[6] = 1)

    ()()() 连续匹配了 3 个括号,所以 (tot[6] = 3)

    (tot) 的统计是为了方便更新 (f) 数组。例如: ()()( 如果再添加一个 ) ,那么这个新组成的括号可以与前面的 ()()() 形成新的合法括号序列

    转移

    (s[u] = '(')(last[u] = u)(f[u])(tot[u]) 直接继承父亲

    (s[u] = ')') ,并且 (last[u]) 存在(否则无法匹配)

    (last[u] = last[fa[last[u]]])

    (tot[u] = tot[fa[last[u]]] + 1)

    (f[u] = f[fa[u]] + tot[u])

    code

    /*
    work by:Ariel_
    */
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #define int long long
    using namespace std;
    const int N = 5e5 + 5;
    int n, fa[N], f[N], last[N], tot[N], ans, cnt;
    char s[N];
    signed main(){
       n = read();
       scanf("%s", s + 1);
       for (int i = 2; i <= n; i++) cin >> fa[i];
       for (int i = 1; i <= n; i++){
       	   f[i] = f[fa[i]], last[i] = last[fa[i]];
       	   if (s[i] == '(') last[i] = i;
    		else if(s[i] == ')' && last[i]) {
    			tot[i] = tot[fa[last[i]]] + 1;
    			last[i] = last[fa[last[i]]];
    			f[i] += tot[i];
    		}
    		ans ^= i * f[i];
    	 } 
         printf("%lld
    ", ans);
       return 0;
    }
    

    T3

    [CSP-S2019]树上的数

    树形结构

    solution(from: Cryin

    发现题解有点错误,所以还是再写一遍

    subtask1 N (leq) 10

    (O(N!)) 暴力枚举所有的删边顺序,取字典数最小的一组

    subtask2 存在度数为 N - 1 的节点

    显然该图一定是个菊花图

    设菊花图入度为 (N-1) 的点为 (rt)

    每次删边一段必定是 (rt) ,假设删边顺序是 (p)(p_i) 表示第 (i) 次删的点编号。

    (i) 次删的边就是 (rt -> p_i)

    有个性质:每次删边,当前 (p_i) 的点权就确定了 (交换之后就不能 (p_i) ) 就不能换了

    发现 (a_{p_1}) 到了(p_2)(a_{p_2}) 到了(p_3)(a_{p_{n-1}}) 到了(rt)

    显然这是一个环

    (rt->p_1->p_2->p_3...->p_{n-1}->rt)

    要想让字典序最小,我们贪心的选择能得到的最小的数字,并且这个环要不能是多个小环

    如果想让数字 (i)(j) 点的话,那么首先 (j) 这个点没有用过,并且 (j)(p_i) 不在同一个环里(防止提前成环,用并查集维护)

    时间复杂度 (O(N^2))

    subtask3 树是一条链 (Nleq) 160

    按照菊花图的思路,考虑数字 (i) 走向 (j)(考试的时候推出来了,但不会写qwq)

    规定三个点

    • 起点:数字 (i) 所在位置
    • 途经点:从起点到终点路过的那些点
    • 终点:(j) 点所在的位置

    我们先把链 (dfs) 处理一下,记录链上第 (i) 个点是 (a_i)(u) 点是第 (num_u)个。

    我们考虑着三种点有什么特殊的性质

    首先对于一个点,删边顺序有两种,先删掉左后删右或者先删右后删左。

    用一个 (tag) 数组表示一下这个点删边顺序 (tag = 0) 表示这个点没有标记,(tag = 1)表示先删掉左后删右,(tag = 2) 表示先删右后删左

    假如 (num_{p_i} leq num_j)(p_i)(j) 的左边,所以 (i) 要向右走

    起点

    先删右后删左,很显然吧,如果如果先删了左边 (i) 点就跑到左边去啦,并且也不回来了

    途径点

    先删左后删右,先删掉左边是为了把 (i) 点从左边接过来,然后再把 (i) 点从右边送过去

    终点

    先删右后删左,先删掉右边是为了保证 (i) 点到了终点之后不再跑出去,删左边是为了从左边把 (i) 点结果来

    (num_{p_i} > num_j) 反过来就好了

    我们可以对应的打一个 (tag) 上去。 如果想让 (i) 走向点 (j) 那么就要与先前的 ( ext{tag}) 不矛盾

    时间复杂度为 (O(N^3))

    subtask4 树是一条链 (N leq 2000)

    对于数字 (i) 可以 ( ext{dfs}) 遍历整条链找到最小的合法的位置。然后再标记一下。

    枚举每一个数字时间复杂度 (O(N))

    找点和标记的复杂度 $ O(N)$

    总复杂度 (O(N^2))

    subtask5 树形态任意 (N leq 2000)

    和链 $ O(N^2)$ 算法一样考虑对于一个点,连接它的边,删边必然有顺序,而且每个点的删边顺序互不影响,这样,对于每个点的删边顺序我们维护成一个集合,这个顺序集构成一条链的形式

    我们用并查集维护每个点的删边顺序集,当前点的最先删除的边的序号,最后删除点的边的序号,当前点有多少条边未删除。

    (fir_u) 为这个点删边集的最先删除的边。

    (lst_u) 为这个点删边集的最后删除的边。

    (pre_p) 表示这条边的前驱。

    (nxt_p) 表示这条边后继。

    同样考虑将数字 (i) 点移到 (j) 点,考虑 起点, 途径点, 终点

    起点

    考虑起点所连的所有边的删边顺序

    1.1: (i) 点指向 (j) 点的边肯定要第一个被删,否则 (i) 就偏离轨道了

    1.2: 如果当前点最后删的边 (last_u) 已经确定,如果 (last_u)(fir_u) 已经在同一个集合里面了,但是这个点的度数 > 1,说明有的边要么在起点之前,要么在起点之后,那么该点的删边序列就不再是一个链了,所以这种情况要排除

    途经点

    2.1: (i) 走到 (j) 经过的点 (u) ,并且走过 ((v,u))((u,w)) 这两条边,并且分别标号为 (p_1)(p_2),首先这两条边的编号一定是连续的,并且一定是先删 (p_1), 再删 (p_2)

    2.2: ((v,u))((u,w)) 两条边已经在这个点的边集合中了,(p_1)(p_2) 显然不合法,因为如果加入就会成环了

    2.3: (pre_{p2}) 已经确定,或者 (nxt_{p1}) 已经确定,显然不合法,因为两条边连不到一起去了

    2.4: (fir_u)(las_u) 已经确定,并且 (fir_u)(p_1) 在同一个集合,(las_u)(p_2)在同一个集合,那么就会直接连成一个环,无法再形成一个链了(除非该点只剩两条边没被删)

    终点

    如果它是终点,显然它不能当起点, 从 (i) 过来的那条边一定是最后删滴

    3.1: 终点 (u) 最后删除的边 (lst_u) 要么为 0,要么为 (p) (从 (i) 过来的边)

    3.2: 如果 (p) 边有后继边不能当做终点 ((nxt_p eq 0))

    3.3: 为了避免提前成环,所以 (fir_u)(p) 在同一个集合的时候,该点的入度只能为 1,也就是只有 (p) 边没有加入集合

    code

    /*
    work by:Ariel_
    */
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    #include <algorithm>
    #define int long long
    using namespace std;
    
    const int N = 2000 + 50;
    const int M = 4000 + 50;
    
    int read(){
        int x = 0,f = 1; char c = getchar();
        while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
        return x*f;
    }
    int T, n, ind[N], head[N], E, Max_In, x[N], y[N], p[N];
    struct edge {
       int u, v, nxt;
    } e[M];
    
    void add_edge (int u, int v) {
       e[++E] = (edge){u, v, head[u]};
       head[u] = E;
    }
    
    void Clear () {
       E = 1;
       for (int i = 0; i <= n; i++) head[i] = ind[i] = 0; 
    }
    namespace subtask2 {//菊花图 
        int fa[N], ans[N];
        bool vis[N];
        int find (int x) {return x == fa[x] ? x : fa[x] = find (fa[x]);}
        void merge (int x, int y) {fa[find (x)] = find (y);}
        void main () {
            for (int i = 1; i <= n; i ++) fa[i] = i, vis[i] = 0;
            for (int i = 1; i <= n; i ++)//数字 
                for (int j = 1; j <= n; j ++)//放置的点的编号 
                    if (!vis[j] && (i == n || find (j) != find (p[i]))) {//确定能构成一个大的环,防止提前结成环 
                         ans[i] = j; vis[j] = 1; merge (j, p[i]);
                         break;
                    }
            for (int i = 1; i <= n; i ++) printf ("%lld%c", ans[i], i == n ? '
    ' : ' ');
        }
    }
    //起点:先右后左, 途径点:先左后右, 终点:先右后左 
    //tag = 0 :没有标记,tag = 1 先左后右,tag = 2 先右后左: 
    //namespace subtask3 {//链 
    //	int cnt, a[N], ans[N], tag[N], num[N];// 
    //	bool vis[N];
    //	void dfs (int u, int f) {//预处理一下链 
    //	    a[num[u] = ++cnt] = u;
    //	    for (int i = head[u]; i; i = e[i].nxt) {
    //	    	 int v = e[i].v;
    //	    	 if (v == f) continue;
    //	    	 dfs (v, u);
    //		}
    //	}
    //    bool check_l (int p1, int p2) { //判断p1->p2的可行性
    //        if (tag[p1] == 1 || tag[p2] == 1) return false;
    //        for (int i = p1 + 1; i < p2; i ++ ) if (tag[i] == 2) return false;
    //        return true;
    //    }
    //    void push_l (int p1, int p2) { //打标记
    //        if (p1 != 1 && p1 != n) tag[p1] = 2;
    //        if (p2 != 1 && p2 != n) tag[p2] = 2;
    //        for (int i = p1 + 1; i < p2; i ++ ) tag[i] = 1;
    //    }
    //    bool check_r (int p1, int p2) {
    //        if (tag[p1] == 2 || tag[p2] == 2) return false;
    //        for (int i = p2 + 1; i < p1; i ++ ) if (tag[i] == 1) return false;
    //        return true;
    //    }
    //    void push_r (int p1, int p2) {
    //        if (p1 != 1 && p1 != n) tag[p1] = 1;
    //        if (p2 != 1 && p2 != n) tag[p2] = 1;
    //        for (int i = p2 + 1; i < p1; i ++ ) tag[i] = 2;        
    //    }
    //    void main() {
    //    	for (int i = 1; i <= n; i ++ ) tag[i] = vis[i] = 0; cnt = 0;//预处理一下 
    //        for (int i = 1; i <= n; i ++ ) if (ind[i] == 1) {dfs (i, 0); break;}
    //        for (int i = 1; i <= n; i++) {//和菊花图一个样,贪心找点替换 
    //           for (int j = 1; j <= n; j++) {
    //           	    bool flag = false;
    //                if (num[p[i]] <= num[j]) 
    //                     if (check_l (num[p[i]], num[j])) push_l (num[p[i]], num[j]), flag = true;
    //                
    //				else  if (check_r (num[p[i]], num[j])) 
    //				    push_r (num[p[i]], num[j]), flag = true;
    //                
    //				if (flag) {ans[i] = j; vis[j] = 1;break;}
    //		   } 
    //		}
    //	   for (int i = 1; i <= n; i ++ ) printf ("%lld%c", ans[i], i == n ? '
    ' : ' '); 
    //	}
    //}
    //起点:先右后左, 途径点:先左后右, 终点:先右后左 
    //tag = 0 :没有标记,tag = 1 先左后右,tag = 2 先右后左: 
    namespace subtask4{//链的 n^2  
    	int cnt, a[N], ans[N], tag[N], num[N];
        bool vis[N];
        void dfs (int u, int f) {//预处理一下链 
    	    a[num[u] = ++cnt] = u;
    	    for (int i = head[u]; i; i = e[i].nxt) {
    	    	 int v = e[i].v;
    	    	 if (v == f) continue;
    	    	 dfs (v, u);
    		}
    	}
        void push_l (int p1, int p2) { //根据每个点的规定打标记 
            if (p1 != 1 && p1 != n) tag[p1] = 2;
            if (p2 != 1 && p2 != n) tag[p2] = 2;
            for (int i = p1 + 1; i < p2; i ++) tag[i] = 1;
        }
        void push_r (int p1, int p2) {
            if (p1 != 1 && p1 != n) tag[p1] = 1;//序列两端特判 
            if (p2 != 1 && p2 != n) tag[p2] = 1;
            for (int i = p2 + 1; i < p1; i ++ ) tag[i] = 2;        
        }
        int find_l (int u) {//左侧的最优点 
            int res = n + 1;
            if (tag[num[u]] == 2) return res;
            for (int j = num[u] - 1; j >= 1; j -- ) {
                if (tag[j] == 1) {
                    if (!vis[j]) res = min (res, a[j]);
                    break;
                } 
                if (tag[j] == 0 && !vis[j]) res = min (res, a[j]);
            }
            return res;
        }
        int find_r (int u) {//右侧的最优点 
            int res = n + 1;
            if (tag[num[u]] == 1) return res;
            for (int j = num[u] + 1; j <= n; j ++ ) {
                if (tag[j] == 2) {
                    if (!vis[j]) res = min (res, a[j]);
                    break;
                }
                if (tag[j] == 0 && !vis[j]) res = min (res, a[j]);
            }
            return res;
        }
    	void main() {
    	   for (int i = 1; i <= n; i++) tag[i] = vis[i] = 0;cnt = 0;
    	   for (int i = 1; i <= n; i++) if(ind[i] == 1) {dfs(i, 0); break;}
    	    for (int i = 1; i <= n; i ++ ) {
                int rt = find_l (p[i]), tp;
                if (rt < (tp = find_r(p[i]))) push_r(num[p[i]], num[rt]); 
                else rt = tp, push_l(num[p[i]], num[rt]);
                ans[i] = rt;
                vis[num[rt]] = 1;
            }
            for (int i = 1; i <= n; i ++ ) printf ("%d%c", ans[i], i == n ? '
    ' : ' ');
    	} 
    }
    namespace subtask5 {//树的任意形态 
        struct UnionFindSet {//每个点的信息 
            int fa[N], fir, lst;
            bool pre[N], nxt[N];
            void build () { 
                for (int i = 1; i <= n; i ++ ) pre[i] = nxt[i] = false, fa[i] = i;
                fir = lst = 0;
            }
            int find (int x) {
                return x == fa[x] ? x : fa[x] = find (fa[x]);
            }
            bool same (int x, int y) {
                return find (x) == find (y);
            }
            void merge (int x, int y) {
                fa[find (x)] = find (y);
            }
        } t[N];
        int dfs (int u, int f) {
            int res = n + 1;
            if (f && (!t[u].lst || t[u].lst == f) ) {//3.1
               if (!t[u].nxt[f] && !(t[u].fir && ind[u] > 1 && t[u].same (f, t[u].fir)))//3.2,3.3
                    res = u;
                }//判断终点 
            for (int i = head[u]; i; i = e[i].nxt) {
                int v = e[i].v, id = i >> 1;//id为这条边的编号 
                if (f == id) continue;
                if (!f) {
                   if (!t[u].fir || t[u].fir == id) {//1.1 
                         if (t[u].pre[id]) continue;//1.1
                         if (t[u].lst && ind[u] > 1 && t[u].same (t[u].lst, id)). continue;//1.2
                            res = min(res, dfs (v, id));
                      } 
    			  else continue;
                }//判断起点 
    			else {
                    if (t[u].fir == id || t[u].lst == f || t[u].same (id, f)) continue;//2.1,2.2
                    if (t[u].pre[id] || t[u].nxt[f]) continue;//2.3 
                    if (t[u].fir && t[u].lst && ind[u] > 2 && t[u].same (t[u].fir, f) && t[u].same (t[u].lst, id)) continue;//2.4
                        res = min(res, dfs (v, id));
                    }
                }
                return res;
            }
        bool push (int u, int f, int end) {//删边(染色) 
            if (u == end) {
                t[u].lst = f;
                return true; 
            }
            for (int i = head[u]; i; i = e[i].nxt) {
                int id = i >> 1, v = e[i].v;
                if (id == f) continue;
                if (push (v, id, end)) {
                    if (!f) t[u].fir = id;//起点 
                    else {
                       t[u].nxt[f] = t[u].pre[id] = true; ind[u]--;
                       t[u].merge(f, id);
                    }
                    return true;
                }
            }
            return false;
        }
        void main () {
            for (int i = 1; i <= n; i ++ ) t[i].build();
            for (int i = 1; i <= n; i ++ ) {
                int ret = dfs (p[i], 0);
                push (p[i], 0, ret);
                printf ("%d%c", ret, i == n ? '
    ' : ' ');
            }
        }
    }
    signed main(){
       T = read();
       while (T--) {
       	  n = read();
       	  Clear();
       	  for (int i = 1; i <= n; i++) p[i] = read();
    	  for (int i = 1, u, v; i < n; i++) {
    	  	   u = read(), v = read();
    	  	   x[i] = u, y[i] = v;
    	  	   add_edge(u, v), add_edge(v, u);
    		   ind[u]++, ind[v]++;
    		   Max_In = max(Max_In, max(ind[u], ind[v])); 	 
    	  }
    	  if(Max_In == n - 1) subtask2::main();   
    	  else if(Max_In == 2) subtask4::main();
    	  else subtask5::main(); 
       }
       puts("");
       return 0;
    }
    
  • 相关阅读:
    [kuangbin带你飞]专题十六 KMP & 扩展KMP & ManacherK
    [kuangbin带你飞]专题十六 KMP & 扩展KMP & Manacher J
    [kuangbin带你飞]专题十六 KMP & 扩展KMP & Manacher I
    pat 1065 A+B and C (64bit)(20 分)(大数, Java)
    pat 1069 The Black Hole of Numbers(20 分)
    pat 1077 Kuchiguse(20 分) (字典树)
    pat 1084 Broken Keyboard(20 分)
    pat 1092 To Buy or Not to Buy(20 分)
    pat 1046 Shortest Distance(20 分) (线段树)
    pat 1042 Shuffling Machine(20 分)
  • 原文地址:https://www.cnblogs.com/Arielzz/p/14843031.html
Copyright © 2011-2022 走看看