• 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;
    }
    
  • 相关阅读:
    Swift中函数
    Swift 中的开关语句switch在swift中的使用
    Swift 函数新特性
    Swift 学习-多线程
    安卓学习
    ios -网络
    ios 中block
    Lua 简易调试
    iOS、Cocos2dx、Unity3D下的坐标系统简介
    Lua开发过程中遇到的一些小问题
  • 原文地址:https://www.cnblogs.com/Arielzz/p/14843031.html
Copyright © 2020-2023  润新知