C# Solution using DSU & MST || Easy to understand
\npublic class Solution {\n    public class Edge{\n        public int id{get;set;}\n        public int from{get;set;}\n        public int to{get;set;}\n        
```\npublic class Solution {\n    public class Edge{\n        public int id{get;set;}\n        public int from{get;set;}\n        public int to{get;set;}\n        public int cost{get;set;}\n    }\n    class DSU{\n        int[] par, sz;\n        public int componentCount;\n        public DSU(int n){\n            par = new int[n];\n            componentCount = n;\n            sz = new int[n];\n            for(int i = 0; i < n;i++){\n                par[i] = i;\n                sz[i] = 1;\n            }\n        }\n        public int FindParent(int u){\n            if(par[u] == u) return u;\n            int p = FindParent(par[u]);\n            par[u] = p;\n            return p;\n        }\n        public bool IsSameComponent(int u , int v) => FindParent(u) == FindParent(v);\n        public void Join(int u,int v){\n            u = FindParent(u);\n            v = FindParent(v);\n            if(u == v) return;\n            if(sz[u] < sz[v]) (u,v) = (v,u);\n            par[v] = u;\n            sz[u]+=sz[v];\n            componentCount--;\n        }\n    }\n    int kruskal(PriorityQueue<Edge,int> edges, int nodes,Edge taken = null){\n        int res = 0;\n        DSU dsu = new DSU(nodes);\n        if(taken != null){\n            dsu.Join(taken.from, taken.to);\n            res += taken.cost;\n        }\n        while(dsu.componentCount > 1 && edges.Count > 0){\n            var e = edges.Dequeue();\n            if(dsu.IsSameComponent(e.from, e.to)) continue;\n            dsu.Join(e.from, e.to);\n            res += e.cost;\n        }\n        if(dsu.componentCount > 1) return Int32.MaxValue;\n        return res;\n    }\n    public PriorityQueue<Edge,int> GetEdges(int[][] edges,int skipedIdx = -1){\n        PriorityQueue<Edge,int> edgesPq = new(Comparer<int>.Create((a, b) => a.CompareTo(b)));\n        for(int i = 0;i < edges.Length;i++){\n            if(i == skipedIdx) continue;\n            edgesPq.Enqueue(new Edge{id = i,from = edges[i][0], to = edges[i][1],cost = edges[i][2]},edges[i][2]);\n        }\n        return edgesPq;\n    }\n    public IList<IList<int>> FindCriticalAndPseudoCriticalEdges(int n, int[][] edges) {\n        int mstCost =  kruskal(GetEdges(edges),n);\n        List<IList<int>> res = new List<IList<int>>(){new List<int>(),new List<int>()};\n        for(int i = 0; i < edges.Length;i++){\n            int curCost = kruskal(GetEdges(edges,i),n);\n            if(curCost > mstCost) res[0].Add(i);\n            else{\n                Edge taken = new Edge{id = i,from = edges[i][0], to = edges[i][1],cost = edges[i][2]};\n                curCost = kruskal(GetEdges(edges),n,taken);\n                if(curCost == mstCost)res[1].Add(i);\n            } \n        }\n        return res;\n    }\n}\n\n\n\n```
['Union Find', 'Minimum Spanning Tree', 'C#']