• 【题解】联合省选 2021 题解


    A. D1T1 卡牌游戏

    可以枚举一个 (p),然后限制最小值 (geq p),接着找到最小的 (q) 使得限制最大值 (leq q) 是合法的。

    那么这个限制就相当于:

    • 对于 (a_i < p),必须要满足 (p leq b_i leq q),记这类点数为 (c_1)
    • 对于 (a_i > q),必须要满足 (p leq b_i leq q),记这类点数为 (c_2)
    • (c_1+c_2 leq m)

    (p) 增加的时候,最优 (q) 必然是不下降的。双指针移动一下,注意实现。时间复杂度 (mathcal O(n log n))(mathcal O(n))

    code

    #include <bits/stdc++.h>
    
    using std::cerr; 
    
    template <class T>
    inline void read(T &x) {
    	static char ch; 
    	while (!isdigit(ch = getchar())); 
    	x = ch - '0';
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0';
    }
    
    template <class T>
    inline void tense(T &x, const T &y) {
    	if (x > y) x = y; 
    }
    
    template <class T>
    inline void relax(T &x, const T &y) {
    	if (x < y) x = y; 
    }
    
    const int MaxN = 1e6 + 5; 
    const int MaxM = 2e6 + 5; 
    const int INF = 0x3f3f3f3f; 
    
    int n, m; 
    int a[MaxN], b[MaxN]; 
    int preMax[MaxN], preMin[MaxN]; 
    int sufMax[MaxN], sufMin[MaxN]; 
    
    int nV, val[MaxM]; 
    
    int main() {
    	freopen("card.in", "r", stdin);
    	freopen("card.out", "w", stdout); 
    
    	read(n), read(m); 
    	for (int i = 1; i <= n; ++i) read(a[i]), val[++nV] = a[i]; 
    	for (int i = 1; i <= n; ++i) read(b[i]), val[++nV] = b[i]; 
    
    	std::sort(val + 1, val + nV + 1); 
    
    	preMax[0] = sufMax[n + 1] = 0; 
    	preMin[0] = sufMin[n + 1] = INF; 
    	for (int i = 1; i <= n; ++i) {
    		a[i] = std::lower_bound(val + 1, val + nV + 1, a[i]) - val; 
    		b[i] = std::lower_bound(val + 1, val + nV + 1, b[i]) - val; 
    		relax(preMax[i] = preMax[i - 1], b[i]); 
    		tense(preMin[i] = preMin[i - 1], b[i]); 
    	}
    
    	for (int i = n; i >= 1; --i) {
    		relax(sufMax[i] = sufMax[i + 1], b[i]); 
    		tense(sufMin[i] = sufMin[i + 1], b[i]); 
    	}
    
    	int res = INF; 
    	for (int l = 1, r = 0, c1 = 0, c2 = n; l <= nV; ++l) {
    		while (c1 < n && a[c1 + 1] < l) ++c1; 
    		
    		if (preMin[c1] < l || c1 > m) break; 
    
    		while (
    			(c1 + c2 > m) 
    			|| (preMax[c1] > r) 
    			|| (sufMin[n - c2 + 1] < l || sufMax[n - c2 + 1] > r)
    		) {
    			++r; 
    			while (c2 > 0 && a[n - c2 + 1] <= r) --c2; 
    		}
    
    		tense(res, val[r] - val[l]); 
    	}
    
    	printf("%d
    ", res); 
    
    	return 0; 
    }
    

    B. D1T2 矩阵游戏

    思路一

    首先,如果确定了第一行和第一列,那么整个矩阵就可以确定了。

    一通观察+归纳证明,不难发现

    [a_{i,j} = K_{i,j}+(-1)^{j+1}a_{i,1}+(-1)^{i+1}a_{1,j}+(-1)^{i+j+1}a_{1,1} ]

    其中 (K_{i,j}) 是可以通过 (b) 矩阵唯一确定的一个常数。

    不难发现,需要处理的就是 (a_{i,j} in [0,10^6]) 的限制,就相当于:

    [K_{i,j}+(-1)^{j+1}a_{i,1}+(-1)^{i+1}a_{1,j}+(-1)^{i+j+1}a_{1,1} in [0,10^6] ]

    这个形式对我们很有启发,两边同时乘上 ((-1)^{i+j+1}),尝试将每一项整理一下

    [(-1)^{i+j+1}K_{i,j}+(-1)^{i}a_{i,1}+(-1)^{j}a_{1,j}+a_{1,1} \ in egin{cases}[0,10^6] & i+j+1 equiv 0 pmod 2 \ [-10^6,0] & i+j+1 equiv 1 pmod 2end{cases} ]

    (a_{1,1}) 随便和 ((-1)^{i}a_{i,1}) 放到一起,这下就可以把 (i,j) 的变量分成两部分了。不难写成差分约束的形式:

    [left((-1)^{i}a_{i,1}+a_{1,1} ight)-(-1)^{j+1}a_{1,j} \ in egin{cases}[-K_{i,j},10^6-K_{i,j}] & i+j+1 equiv 0 pmod 2 \ [K_{i,j}-10^6,K_{i,j}] & i+j+1 equiv 1 pmod 2end{cases} ]

    这下就可以做一个 (mathcal O(n+m)) 个点 (mathcal O(nm)) 条边的差分约束了。

    时间复杂度 (mathcal O(T(n+m)nm)),SPFA 跑不满。

    思路二

    我看了看别人的题解,学到了一个明显更简单的思路。

    将第一行、第一列都赋成 (0),先得到一个满足矩阵 (b),但是不一定满足范围的解,记为 (p)

    利用若干次以下两类操作,我们可以用矩阵 (p) 构造出任意一组满足矩阵 (b) 的解:

    • 操作一:选择其中一行,依次将每个数 (+1,-1,+1,cdots)(权值记为 (+1))或 (-1,+1,-1,cdots)(权值记为 (-1)
    • 操作二:选择其中一列,依次将每个数 (+1,-1,+1,cdots)(权值记为 (+1))或 (-1,+1,-1,cdots)(权值记为 (-1)

    因为两种操作完全可以将第一行、第一列修改成任意情况,所以整个矩阵 (a) 的所有可能情况都可以达到。

    记第 (i) 行进行的操作一总权值为 (c_i),第 (j) 列进行的操作二总权值为 (d_i)。那么

    [a_{i,j}=p_{i,j}+(-1)^{j+1} c_i+(-1)^{i+1} d_j ]

    同「思路一」操作一下,就转化成差分约束了,这里就不赘述了。

    code(思路一)(这份边数代码太多被卡常了)

    #include <bits/stdc++.h>
    
    #define foredge(u) for (int e = adj[u], v; v = to[e], e; e = nxt[e])
    
    using std::cerr; 
    typedef long long s64; 
    
    template <class T>
    inline void read(T &x) {
    	static char ch; 
    	while (!isdigit(ch = getchar())); 
    	x = ch - '0'; 
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0'; 
    }
    
    template <class T>
    inline void putint(T x) {
    	static char buf[15], *tail = buf; 
    	if (!x) putchar('0'); 
    	else {
    		if (x < 0) x = ~x + 1, putchar('-'); 
    		for (; x; x /= 10) *++tail = x % 10 + '0'; 
    		for (; tail != buf; --tail) putchar(*tail); 
    	}
    }
    
    template <class T>
    inline bool tense(T &x, const T &y) {
    	return x > y ? x = y, true : false; 
    }
    
    template <class T>
    inline bool relax(T &x, const T &y) {
    	return x < y ? x = y, true : false; 
    }
    
    const int MaxN = 3e2 + 5; 
    const int MaxV = 7e2 + 5; 
    const int MaxE = MaxN * MaxN * 2 + MaxV * 2; 
    
    int n, m, nV, lim = 1000000; 
    s64 b[MaxN][MaxN], dis[MaxV]; 
    
    bool flgCir; 
    
    int ect, nxt[MaxE], to[MaxE], weight[MaxE]; 
    int adj[MaxV]; 
    
    inline void init() {
    	ect = 0; 
    	for (int i = 1; i <= nV; ++i) adj[i] = 0; 
    }
    
    inline void addEdge(int u, int v, int w) {
    	nxt[++ect] = adj[u]; 
    	adj[u] = ect; 
    	to[ect] = v; 
    	weight[ect] = w;  
    }
    
    inline void addSeg(int u, int v, s64 l, s64 r) {
    	relax(l, -3LL * lim), tense(r, 3LL * lim); 
    	if (l > r) flgCir = true; 
    
    	addEdge(u, v, (int)r); 
    	addEdge(v, u, -(int)l); 
    }
    
    void SPFA() {
    	static int que[MaxV * MaxV], cnt[MaxV], qr; 
    	static bool inq[MaxV]; 
    
    	for (int i = 1; i <= nV; ++i) {
    		cnt[i] = 0;  //!!!
    		inq[i] = false; 
    		dis[i] = 1LL << 60; 
    	}
    
    	inq[nV] = true; 
    	dis[que[qr = 1] = nV] = 0; 
    	for (int i = 1; i <= qr; ++i) {
    		int u = que[i]; 
    		inq[u] = false; 
    
    		foredge(u) if (tense(dis[v], dis[u] + weight[e])) {
    			if ((cnt[v] = cnt[u] + 1) >= nV) {
    				flgCir = true; 
    				return; 
    			}
    
    			if (!inq[v]) inq[que[++qr] = v] = true; 
    		}
    	}
    }
    
    void work() {
    	init(); 
    	flgCir = false; 
    
    	read(n), read(m), nV = n + m; 
    	
    	addSeg(nV, 1, 0, lim); 
    	for (int i = 2; i <= n; ++i) {
    		if (i & 1)
    			addSeg(1, i, -lim, 0); 
    		else
    			addSeg(1, i, 0, lim); 
    	}
    
    	for (int i = 2; i <= m; ++i) {
    		int p = i + n - 1; 
    		if (i & 1)
    			addSeg(nV, p, 0, lim); 
    		else
    			addSeg(nV, p, -lim, 0); 
    	}
    
    	for (int i = 2; i <= n; ++i)
    		for (int j = 2; j <= m; ++j) {
    			read(b[i][j]); 
    			b[i][j] -= b[i - 1][j]; 
    			b[i][j] -= b[i][j - 1]; 
    			b[i][j] -= b[i - 1][j - 1]; 
    
    			if ((i + j + 1) & 1)
    				addSeg(j + n - 1, i, -lim + b[i][j], +b[i][j]); 
    			else
    				addSeg(j + n - 1, i, -b[i][j], -b[i][j] + lim); 
    		}
    	
    	if (flgCir) return (void)(puts("NO")); 
    	SPFA(); 
    	if (flgCir) return (void)(puts("NO")); 
    
    	puts("YES");  
    	for (int i = 1; i <= n; ++i)
    		for (int j = 1; j <= m; ++j) {
    			int res = 
    			b[i][j] 
    			+ ((i + j + 1) & 1 ? -1 : 1) * dis[i]
    			+ ((i + j) & 1 ? -1 : 1) * dis[j + n - 1]; 
    			
    			if (i == 1) {
    				res = (j & 1 ? 1 : -1) * dis[j == 1 ? 1 : j + n - 1]; 
    			} else if (j == 1) {
    				res = (i & 1 ? -1 : 1) * (dis[i] - dis[1]); 
    			}
    
    			putint(res), putchar(" 
    "[j == m]); 
    		}
    }
    
    int main() {
    	freopen("matrix.in", "r", stdin); 
    	freopen("matrix.out", "w", stdout); 
    
    	int T; 
    	read(T); 
    	while (T--) work(); 
    
    	return 0; 
    }
    

    code(思路一)(这份用邻接表存边应该不会被卡了)

    #include <bits/stdc++.h>
    
    #define foredge(u) for (int e = adj[u], v; v = to[e], e; e = nxt[e])
    
    using std::cerr; 
    typedef long long s64; 
    
    template <class T>
    inline void read(T &x) {
    	static char ch; 
    	while (!isdigit(ch = getchar())); 
    	x = ch - '0'; 
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0'; 
    }
    
    template <class T>
    inline void putint(T x) {
    	static char buf[15], *tail = buf; 
    	if (!x) putchar('0'); 
    	else {
    		if (x < 0) x = ~x + 1, putchar('-'); 
    		for (; x; x /= 10) *++tail = x % 10 + '0'; 
    		for (; tail != buf; --tail) putchar(*tail); 
    	}
    }
    
    template <class T>
    inline bool tense(T &x, const T &y) {
    	return x > y ? x = y, true : false; 
    }
    
    template <class T>
    inline bool relax(T &x, const T &y) {
    	return x < y ? x = y, true : false; 
    }
    
    const int MaxN = 3e2 + 5; 
    const int MaxV = 7e2 + 5; 
    const int MaxE = MaxN * MaxN * 2 + MaxV * 2; 
    
    const int INF = 0x3f3f3f3f; 
    
    int n, m, nV, lim = 1000000; 
    s64 b[MaxN][MaxN], dis[MaxV]; 
    
    bool flgCir; 
    
    int ect, nxt[MaxE], to[MaxE], weight[MaxE]; 
    int adj[MaxV]; 
    
    int g[MaxV][MaxV]; 
    
    inline void init() {
    	ect = 0; 
    	for (int i = 1; i <= nV; ++i) adj[i] = 0; 
    	for (int i = 1; i <= nV; ++i)
    		for (int j = 1; j <= nV; ++j) {
    			g[i][j] = INF; 
    		}
    }
    
    inline void addEdge(int u, int v, int w) {
    	tense(g[u][v], w); 
    }
    
    inline void addSeg(int u, int v, s64 l, s64 r) {
    	relax(l, -3LL * lim), tense(r, 3LL * lim); 
    	if (l > r) flgCir = true; 
    
    	addEdge(u, v, (int)r); 
    	addEdge(v, u, -(int)l); 
    }
    
    void SPFA() {
    	static int que[MaxV * MaxV], cnt[MaxV], qr; 
    	static bool inq[MaxV]; 
    
    	for (int i = 1; i <= nV; ++i) {
    		cnt[i] = 0;  //!!!
    		inq[i] = false; 
    		dis[i] = 1LL << 60; 
    	}
    
    	inq[nV] = true; 
    	dis[que[qr = 1] = nV] = 0; 
    	for (int i = 1; i <= qr; ++i) {
    		int u = que[i]; 
    		inq[u] = false; 
    
    		for (int v = 1; v <= nV; ++v) if (tense(dis[v], dis[u] + g[u][v])) {
    			if ((cnt[v] = cnt[u] + 1) >= nV) {
    				flgCir = true; 
    				return; 
    			}
    
    			if (!inq[v]) inq[que[++qr] = v] = true; 
    		}
    	}
    }
    
    void work() {
    	read(n), read(m), nV = n + m; 
    
    	init(); 
    	flgCir = false; 
    
    	addSeg(nV, 1, 0, lim); 
    	for (int i = 2; i <= n; ++i) {
    		if (i & 1)
    			addSeg(1, i, -lim, 0); 
    		else
    			addSeg(1, i, 0, lim); 
    	}
    
    	for (int i = 2; i <= m; ++i) {
    		int p = i + n - 1; 
    		if (i & 1)
    			addSeg(nV, p, 0, lim); 
    		else
    			addSeg(nV, p, -lim, 0); 
    	}
    
    	for (int i = 2; i <= n; ++i)
    		for (int j = 2; j <= m; ++j) {
    			read(b[i][j]); 
    			b[i][j] -= b[i - 1][j]; 
    			b[i][j] -= b[i][j - 1]; 
    			b[i][j] -= b[i - 1][j - 1]; 
    
    			if ((i + j + 1) & 1)
    				addSeg(j + n - 1, i, -lim + b[i][j], +b[i][j]); 
    			else
    				addSeg(j + n - 1, i, -b[i][j], -b[i][j] + lim); 
    		}
    	
    	if (flgCir) return (void)(puts("NO")); 
    	SPFA(); 
    	if (flgCir) return (void)(puts("NO")); 
    
    	puts("YES"); 
    	for (int i = 1; i <= n; ++i)
    		for (int j = 1; j <= m; ++j) {
    			int res = 
    			b[i][j] 
    			+ ((i + j + 1) & 1 ? -1 : 1) * dis[i]
    			+ ((i + j) & 1 ? -1 : 1) * dis[j + n - 1]; 
    			
    			if (i == 1) {
    				res = (j & 1 ? 1 : -1) * dis[j == 1 ? 1 : j + n - 1]; 
    			} else if (j == 1) {
    				res = (i & 1 ? -1 : 1) * (dis[i] - dis[1]); 
    			}
    
    			putint(res), putchar(" 
    "[j == m]); 
    		}
    }
    
    int main() {
    	freopen("matrix.in", "r", stdin); 
    	freopen("matrix.out", "w", stdout); 
    
    	int T; 
    	read(T); 
    	while (T--) work(); 
    
    	return 0; 
    }
    

    C. D1T3 图函数

    首先不难得到一个结论:在计算 (f(u,G)) 的过程中,如果我们按顺序枚举到一个点 (v),那么在 (v) 前面的点 (w) 如果没删掉,删掉 (w) 也不会改变我们的判断结果。

    这是因为,我们在判断 (v) 是否会在 (f(u,G)) 中产生贡献的时候,实际上是判断 (u,v) 在当前的图上是否属于同一个强连通分量。而之前没有删掉的点 (w)(u) 一定就不在一个强连通分量中,删掉它不影响我们的判断。

    于是 (f(u,G)= sum_{v} [ ext{删掉 } 1 sim v-1 ext{ 的点后 } u,v ext{ 仍然连通}])

    记命题 (p(i,u,v)) 表示 (u,v) 之间存在一条路径,使得这条路径经过的点编号都 (geq i)

    显然使得 (p(i,u,v)) 成立的图一定是 (G_0sim G_m) 的一段前缀。如果能对于每个点对 ((u,v)) 找到最大的 (e),使得 (p(v,u,v)land p(v,v,u))(G_0sim G_{e-1}) 都成立,那么就可以将其贡献到 (h(G_0)sim h(G_{e-1})) 中。

    现在的问题就是如何找到这个 (e),下面一一介绍。

    思路一

    注意到满足 (p(u,u,v)land p(u,v,u)) 这个条件的最大的 (e) 可以看成,在正图 (G) 和反图 (G^R)(u) 经过 (geq u) 的点走到 (v) 的所有路径中,分别找到路径最小边权(边的编号)的最大值

    一个想法就是枚举起点 (u),然后删去 (< u) 的点,以它为起点跑 Dijkstra。时间复杂度 (mathcal O(nmlog m))

    实际上限制只能经过编号 (geq u) 的点,可以通过 Floyd 枚举第一维来实现,用 Floyd 求解即可。时间复杂度 (mathcal O(n^3))

    code (O(nm log m))

    #include <bits/stdc++.h>
    
    #define foredge(u, G) for (int e = G.adj[u], v; v = G.to[e], e; e = G.nxt[e])
    
    using std::min; 
    using std::cerr; 
    using std::pair; 
    using std::make_pair; 
    
    typedef std::vector<int> vi; 
    typedef std::pair<int, int> pii; 
    
    template <class T>
    inline void read(T &x) {
    	static char ch; 
    	while (!isdigit(ch = getchar()));
    	x = ch - '0'; 
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0'; 
    }
    
    template <class T>
    inline bool relax(T &x, const T &y) {
    	return x < y ? x = y, true : false; 
    }
    
    const int INF = 0x3f3f3f3f; 
    const int MaxN = 1000 + 5; 
    const int MaxE = 4e5 + 5; 
    
    struct graph {
    	int ect, adj[MaxN]; 
    	int nxt[MaxE], to[MaxE], weight[MaxE]; 
    
    	inline void addEdge(int u, int v, int w) {
    		nxt[++ect] = adj[u]; 
    		adj[u] = ect; 
    		to[ect] = v; 
    		weight[ect] = w; 
    	}
    }G1, G2; 
    
    int n, m; 
    int ans[MaxE]; 
    
    void Dijkstra(int st, int *dis, graph &G) {
    	static std::priority_queue<pii> heap; 
    	static bool vis[MaxN]; 
    
    	for (int i = 1; i <= n; ++i) {
    		dis[i] = 0; 
    		vis[i] = false; 
    	}
    
    	while (!heap.empty()) heap.pop(); 
    	heap.push(make_pair(dis[st] = m + 1, st)); 
    
    	while (!heap.empty()) {
    		int u = heap.top().second; 
    		heap.pop(); 
    
    		if (vis[u]) continue; 
    		vis[u] = true; 
    
    		foredge(u, G)
    			if (v >= st && !vis[v] && relax(dis[v], min(dis[u], G.weight[e])))
    				heap.push(make_pair(dis[v], v)); 
    	}
    }
    
    int main() {
    	freopen("graph.in", "r", stdin); 
    	freopen("graph.out", "w", stdout); 
    
    	read(n), read(m);
    	for (int i = 1; i <= m; ++i) {
    		int u, v; 
    		read(u), read(v); 
    		G1.addEdge(u, v, i); 
    		G2.addEdge(v, u, i); 
    	}
    
    	for (int st = 1; st <= n; ++st) {
    		static int dis1[MaxN], dis2[MaxN]; 
    
    		Dijkstra(st, dis1, G1); 
    		Dijkstra(st, dis2, G2); 
    
    		for (int i = st; i <= n; ++i)
    			++ans[min(dis1[i], dis2[i])]; 
    	}
    
    	for (int i = m; i >= 1; --i)
    		ans[i] += ans[i + 1]; 
    	for (int i = 1; i <= m + 1; ++i) {
    		printf("%d%c", ans[i], " 
    "[i == m + 1]); 
    	}
    	
    	return 0; 
    }
    

    code (mathcal O(n^3))

    #include <bits/stdc++.h>
    
    #define foredge(u) for (halfEdge *e = adj[u]; e; e = e->next)
    
    using std::min; 
    using std::cerr; 
    using std::pair; 
    using std::vector; 
    using std::make_pair; 
    
    typedef std::vector<int> vi; 
    typedef std::pair<int, int> pii; 
    
    template <class T>
    inline void read(T &x) {
    	static char ch; 
    	while (!isdigit(ch = getchar()));
    	x = ch - '0'; 
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0'; 
    }
    
    template <class T>
    inline void relax(T &x, const T &y) {
    	x < y ? x = y : 0; 
    }
    
    const int INF = 0x3f3f3f3f; 
    const int MaxN = 1000 + 5; 
    const int MaxE = 4e5 + 5; 
    
    int n, m; 
    int ans[MaxE]; 
    int f[MaxN][MaxN]; 
    
    int main() {
    	freopen("graph.in", "r", stdin); 
    	freopen("graph.out", "w", stdout); 
    
    	read(n), read(m);
    	for (int i = 1; i <= m; ++i) {
    		int u, v; 
    		read(u), read(v); 
    		relax(f[u][v], i); 
    	}
    
    	for (int k = n; k >= 1; --k) {
    		relax(f[k][k], m + 1); 
    
    		for (int u = 1; u <= n; ++u) if (f[u][k])
    			for (int v = 1, lim = u > k ? k - 1 : n; v <= lim; ++v)
    				relax(f[u][v], min(f[u][k], f[k][v])); 
    		
    		for (int v = k; v <= n; ++v)
    			++ans[min(f[v][k], f[k][v])]; 
    	}
    
    	for (int i = m; i >= 1; --i)
    		ans[i] += ans[i + 1]; 
    	for (int i = 1; i <= m + 1; ++i) {
    		printf("%d%c", ans[i], " 
    "[i == m + 1]); 
    	}
    	
    	return 0; 
    }
    

    思路二

    反过来考虑,我们倒序加边,看看每个点对在加入哪条边的时候恰好连通。

    假设 (f(u,v)) 表示在当前加入的边集中,(u) 能否通过 (geq u) 的点到达 (v)

    倒序枚举到边 ((x,y)) 的时候,再枚举点对的起点 (u(uleq y))

    • 如果 (f(u,x)=1)(f(u,y)=0)
    • 那么就从 (y) 点出发 DFS/BFS
    • 将所有访问到的 (vgeq u)(f(u,v)=0) 的点更新。

    注意到在上述过程中,一个起点 (u) 中每个点 (v) 只会被访问至多一次,因此边也至多访问一次,时间复杂度就是 (mathcal O(nm+n^2))

    实际上,有些枚举是冗余的,因为在枚举过程中实际上用到的点对并不多:

    • 对于枚举的边 ((x,y)),如果枚举的 (u) 满足 (f(u,x)=1)(f(u,y)=0),那么这样的 ((u,y)) 至多有 (mathcal O(n^2)) 对。
    • 对于 DFS/BFS 过程中访问到的点 (v),实际上在枚举的每个起点 (u) 中也只会至多访问一次,枚举出边的过程是冗余的。

    用 bitset 的 _Find_first 和 _Find_next,我们就可以很好地解决冗余枚举的问题。只需要对每个点 (v) 多记一个 (g(v,u)=f(u,v)) 即可。

    总的时间复杂度就可以优化到 (mathcal O(frac{n^3+nm}{omega})),这个应该是比较正确的复杂度,实际效率也是这几种算法中最优的。

    code (O(nm))

    #include <bits/stdc++.h>
    
    #define foredge(u) for (halfEdge *e = adj[u]; e; e = e->next)
    
    using std::min; 
    using std::cerr; 
    using std::pair; 
    using std::make_pair; 
    
    typedef std::vector<int> vi; 
    typedef std::pair<int, int> pii; 
    
    template <class T>
    inline void read(T &x) {
    	static char ch; 
    	while (!isdigit(ch = getchar()));
    	x = ch - '0'; 
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0'; 
    }
    
    template <class T>
    inline bool relax(T &x, const T &y) {
    	return x < y ? x = y, true : false; 
    }
    
    const int INF = 0x3f3f3f3f; 
    const int MaxN = 1000 + 5; 
    const int MaxE = 4e5 + 5; 
    
    struct edge {
    	int u, v; 
    }e1[MaxE], e2[MaxE]; 
    
    struct halfEdge {
    	int v; 
    	halfEdge *next; 
    }adjPool[MaxE], *adjTail = adjPool, *adj[MaxN]; 
    
    int n, m; 
    int ans[MaxE]; 
    
    inline void clear() {
    	adjTail = adjPool; 
    	for (int i = 1; i <= n; ++i) {
    		adj[i] = NULL; 
    	}
    }
    
    inline void addEdge(int u, int v) {
    	adjTail->v = v; 
    	adjTail->next = adj[u]; 
    	adj[u] = adjTail++; 
    }
    
    void bfs(int u, int *dis, int disval) {
    	static int que[MaxN], qr; 
    	
    	dis[que[qr = 1] = u] = disval; 
    	for (int i = 1; i <= qr; ++i) {
    		int u = que[i]; 
    		foredge(u) if (!dis[e->v])
    			dis[que[++qr] = e->v] = disval; 
    	}
    }
    
    void solve(int st, int *dis, edge *e) {
    	for (int i = 1; i <= n; ++i) {
    		dis[i] = 0; 
    	}
    	clear(); 
    
    	dis[st] = m + 1;  
    	for (int i = m; i >= 1; --i) {
    		int u = e[i].u, v = e[i].v; 
    
    		if (u >= st && v >= st) {
    			if (!dis[u] && !dis[v])
    				addEdge(u, v); 
    			else if (dis[u] && !dis[v]) 
    				bfs(v, dis, i); 
    		}
    	}
    }
    
    int main() {
    	freopen("graph.in", "r", stdin); 
    	freopen("graph.out", "w", stdout); 
    
    	read(n), read(m);
    	for (int i = 1; i <= m; ++i) {
    		int u, v; 
    		read(u), read(v); 
    		e1[i] = (edge){u, v}; 
    		e2[i] = (edge){v, u}; 
    	}
    
    	for (int st = 1; st <= n; ++st) {
    		static int dis1[MaxN], dis2[MaxN]; 
    
    		solve(st, dis1, e1); 
    		solve(st, dis2, e2); 
    
    		for (int i = st; i <= n; ++i)
    			++ans[min(dis1[i], dis2[i])]; 
    	}
    
    	for (int i = m; i >= 1; --i)
    		ans[i] += ans[i + 1]; 
    	for (int i = 1; i <= m + 1; ++i) {
    		printf("%d%c", ans[i], " 
    "[i == m + 1]); 
    	}
    	
    	return 0; 
    }
    

    code (mathcal O(frac{n^3+nm}{omega}))

    #include <bits/stdc++.h>
    
    #define foredge(u) for (halfEdge *e = adj[u]; e; e = e->next)
    
    using std::min; 
    using std::cerr; 
    using std::pair; 
    using std::make_pair; 
    
    typedef std::vector<int> vi; 
    typedef std::pair<int, int> pii; 
    
    template <class T>
    inline void read(T &x) {
    	static char ch; 
    	while (!isdigit(ch = getchar()));
    	x = ch - '0'; 
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0'; 
    }
    
    template <class T>
    inline bool relax(T &x, const T &y) {
    	return x < y ? x = y, true : false; 
    }
    
    const int INF = 0x3f3f3f3f; 
    const int MaxN = 1000 + 5; 
    const int MaxE = 4e5 + 5; 
    
    typedef std::bitset<MaxN> bst; 
    
    struct edge {
    	int u, v; 
    }e[2][MaxE]; 
    
    int n, m; 
    int ans[MaxE]; 
    int dis[2][MaxN][MaxN]; 
    
    bst vis[MaxN], bel[MaxN], adj[MaxN]; 
    
    void fillVis(int st, int src, int disVal, int opt) {
    	static int que[MaxN], qr; 
    
    	vis[src][que[qr = 1] = st] = 1; 
    	bel[st][src] = 1; 
    
    	dis[opt][src][st] = disVal; 
    	
    	for (int i = 1; i <= qr; ++i) {
    		int u = que[i]; 
    		
    		bst nxt = (~vis[src]) & adj[u]; 
    		for (int v = nxt._Find_next(src); v <= n; v = nxt._Find_next(v)) {
    			vis[src][que[++qr] = v] = 1; 
    			bel[v][src] = 1; 
    
    			dis[opt][src][v] = disVal; 
    		}
    	}
    }
    
    void solve(int opt) {
    	for (int i = 1; i <= n; ++i) {
    		vis[i].reset(); 
    		bel[i].reset(); 
    		adj[i].reset(); 
    
    		bel[i][i] = vis[i][i] = 1; 
    		dis[opt][i][i] = m + 1; 
    	}
    
    	for (int ie = m; ie >= 1; --ie) {
    		int s = e[opt][ie].u; 
    		int t = e[opt][ie].v; 
    
    		adj[s][t] = 1; 
    
    		bst st = bel[s] & (~bel[t]); 
    		for (int u = st._Find_first(); u <= t; u = st._Find_next(u))
    			fillVis(t, u, ie, opt); 
    	}
    }
    
    int main() {
    	freopen("graph.in", "r", stdin); 
    	freopen("graph.out", "w", stdout); 
    
    	read(n), read(m);
    	for (int i = 1; i <= m; ++i) {
    		int u, v; 
    		read(u), read(v); 
    		e[0][i] = (edge){u, v}; 
    		e[1][i] = (edge){v, u}; 
    	}
    
    	solve(0); 
    	solve(1); 
    
    	for (int i = 1; i <= n; ++i)
    		for (int j = i; j <= n; ++j)
    			++ans[min(dis[0][i][j], dis[1][i][j])]; 
    
    	for (int i = m; i >= 1; --i)
    		ans[i] += ans[i + 1]; 
    	for (int i = 1; i <= m + 1; ++i) {
    		printf("%d%c", ans[i], " 
    "[i == m + 1]); 
    	}
    	
    	return 0; 
    }
    

    D. D2T1 宝石

    算法一

    这个是我自己的思路。

    首先注意题目的条件:「(P_1,P_2,cdots,P_c) 互不相等」。那么每个点就唯一对应排列中的一个编号,每个点的下一个和上一个匹配元素也很容易找到。

    根据这一点,可以设计出一个寻找最长匹配子序列的思路。

    首先,我们求出:

    • (mathrm{par}_1(x)) 表示能在 (x) 的祖先中找到的,点权恰好等于 (x) 在排列中对应的一个元素,且深度最深的点。
    • (mathrm{par}_2(x)) 表示能在 (x) 的祖先中找到的,点权恰好等于 (x) 在排列中对应的一个元素,且深度最深的点。

    这样就得到了两棵新树,可以对两个 (mathrm{par}) 数组建立倍增数组。

    然后对于询问的路径 (u o v),将其拆解为 (u o mathrm{lca} o v)(显然,按照路径中访问的点的顺序依次匹配,肯定是能匹配先匹配更优):

    • 对于 (u o mathrm{lca}) 这一段,我们先找到深度最大的 (P_1) 的点 (x),然后从 (x) 开始在 (mathrm{par}_1) 上倍增跳,跳到在原树中深度最小且 (geq mathrm{dep}_{mathrm{lca}}) 的点。
    • 对于 (mathrm{lca} o v) 这一段,因为我们已经在前面一段找到了能匹配的最长前缀 (P_1sim P_{L_1}),所以可以二分这一段接着能匹配的长度 (L_2)。二分的判断只需要在 (mathrm{lca} o v) 这一段找到最深的 (P_{L_1+L_2}),然后在 (mathrm{par}_2) 上跳 (L_2-1) 步看看深度是否满足 (> mathrm{dep})

    注意上面是把第二段的询问离线后 DFS,在 DFS 的时候用一个桶记录了路径上最后一个点权等于每个值的点。同时注意 LCA 必须只能属于其中一段,在另一端查询的时候要去掉 LCA。

    因为有二分,所以时间复杂度就是 (mathcal O(n log n + qlog nlog m))

    code

    #include <bits/stdc++.h>
    
    using std::swap; 
    using std::cerr; 
    using std::vector; 
    
    typedef std::vector<int> vi; 
    
    template <class T>
    inline void read(T &x) {
    	static char ch;
    	while (!isdigit(ch = getchar())); 
    	x = ch - '0'; 
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0'; 
    }
    
    template <class T>
    inline void putint(T x) {
    	static char buf[15], *tail = buf; 
    	if (!x) putchar('0'); 
    	else {
    		if (x < 0) putchar('-'), x = ~x + 1; 
    		for (; x; x /= 10) *++tail = x % 10 + '0'; 
    		for (; tail != buf; --tail) putchar(*tail); 
    	}
    }
    
    const int MaxN = 2e5 + 5; 
    const int MaxM = 5e4 + 5; 
    const int MaxE = MaxN << 1; 	
    
    const int MaxLog = 18; 
    
    int dep[MaxN]; 
    
    struct tree {
    	int anc[MaxN][MaxLog + 1]; 
    
    	inline void insert(int u, int fa) {
    		anc[u][0] = fa; 
    
    		for (int i = 0; anc[u][i]; ++i)
    			anc[u][i + 1] = anc[anc[u][i]][i]; 
    	}
    	inline int jump(int x, int low) {
    		int res = 0; 
    		for (int i = MaxLog; i >= 0; --i)
    			if (anc[x][i] && dep[anc[x][i]] >= low)
    				res |= 1 << i, x = anc[x][i]; 
    		return res + (dep[x] >= low); 
    	}
    
    	inline int queryLCA(int u, int v) {
    		if (dep[u] < dep[v]) swap(u, v); 
    		for (int d = dep[u] - dep[v], i = 0; d; d >>= 1, ++i)
    			if (d & 1) {
    				u = anc[u][i]; 
    			}
    		if (u == v) return u; 
    	
    		for (int i = MaxLog; i >= 0; --i)
    			if (anc[u][i] != anc[v][i]) {
    				u = anc[u][i]; 
    				v = anc[v][i]; 
    			}
    		return anc[u][0]; 
    	}
    }A, B, T; 
    
    struct request {
    	int d, lca, pos;  // d: u->lca answer
    	request(){}
    	request(int a, int b, int c): d(a) ,lca(b), pos(c) {}
    }; 
    
    int n, m, Q; 
    int p[MaxN], idx[MaxN], c[MaxN]; 
    
    vi adj[MaxN]; 
    
    int lst[MaxN], src[MaxN]; 
    vector<request> req[MaxN]; 
    
    int ans[MaxN]; 
    
    void dfsInit(int u, int pre) {
    	int tlst = lst[c[u]]; 
    	
    	lst[c[u]] = u; 
    	src[u] = lst[p[1]]; 
    	dep[u] = dep[pre] + 1; 
    
    	A.insert(u, lst[p[idx[c[u]] + 1]]); 
    	B.insert(u, lst[p[idx[c[u]] - 1]]); 
    
    	for (int i = 0; i < (int)adj[u].size(); ++i) {
    		int v = adj[u][i]; 
    		if (v == pre) continue; 
    
    		T.insert(v, u); 
    		dfsInit(v, u); 
    	}
    
    	lst[c[u]] = tlst; 
    }
    
    void dfsSolve(int u, int pre) {
    	int tlst = lst[c[u]]; 
    	
    	lst[c[u]] = u; 
    
    	for (int i = 0; i < (int)adj[u].size(); ++i) {
    		int v = adj[u][i]; 
    		if (v == pre) continue; 
    
    		dfsSolve(v, u); 
    	}
    
    	for (int i = 0; i < (int)req[u].size(); ++i) {
    		request R = req[u][i]; 
    
    		int l = R.d + 1, r = m, mid, res = R.d; 
    		int lim = dep[R.lca] + 1; 
    
    		while (l <= r) {
    			mid = (l + r) >> 1; 
    			if (B.jump(lst[p[mid]], lim) >= mid - R.d)
    				l = mid + 1, res = mid; 
    			else
    				r = mid - 1; 
    		}
    
    		ans[R.pos] = res; 
    	}
    
    	lst[c[u]] = tlst; 
    }
    
    int main() {
    	freopen("gem.in", "r", stdin); 
    	freopen("gem.out", "w", stdout); 
    
    	read(n), read(m)/*no use*/, read(m); 
    	for (int i = 1; i <= m; ++i) {
    		read(p[i]); 
    		idx[p[i]] = i; 
    	}
    	for (int i = 1; i <= n; ++i) {
    		read(c[i]); 
    	}
    
    	for (int i = 1; i < n; ++i) {
    		int u, v; 
    		read(u), read(v); 
    
    		adj[u].push_back(v); 
    		adj[v].push_back(u); 
    	}
    
    	dfsInit(1, 0); 
    
    	read(Q); 
    	for (int q = 1; q <= Q; ++q) {
    		int u, v; 
    		read(u), read(v); 
    		
    		int lca = T.queryLCA(u, v); 
    		ans[q] = A.jump(src[u], dep[lca]); 
    
    		// cerr << u << ' ' << v << ':' << lca << ':' << ans[q] << '
    '; 
    
    		if (lca != v) {
    			req[v].push_back(request(ans[q], lca, q)); 
    		}
    	}
    
    	dfsSolve(1, 0); 
    
    	for (int q = 1; q <= Q; ++q) {
    		putint(ans[q]); 
    		putchar('
    '); 
    	}
    
    	return 0; 
    }
    

    算法二

    参考:https://www.luogu.com.cn/blog/hezlik/solution-p7518

    第二段的询问并不需要二分。可以将第一段已经得到的点放到 (mathrm{lca}) 的地方,离线 DFS 的时候用可撤销并查集维护。

    这样总的时间复杂度就是 (mathcal O(n log n + Q log m)) 了。

    算法三

    参考:https://strncmp.blog.luogu.org/solution-p7518

    算法一中的 (mathrm{lca}) 其实可以换一种角度看待。

    用点分治处理所有路径询问,对于跨过某个分治中心 (G) 的询问,就将 (G) 看成算法一中的 (mathrm{lca}) 来处理。

    这样的好处就是,可以直接算出到每个点 (u)(G) 的路径上,在 (P) 中往前和往后分别能跳多少步,就不需要倍增了。

    每个询问的第二段就像算法一那样二分,但是检查的时候也不需要倍增。

    时间复杂度 (mathcal O(n log n + Qlog m))

    E. D2T2 滚榜

    显然,最后排名的顺序就是滚榜顺序的倒序,所以我们只需要对滚榜顺序进行计数即可。

    显然,在滚榜顺序确定的情况下,既然要求通过题数不下降且总和 (=m),那么有一种最优构造就是依次取可能的最小值,多出来的放到最后一个滚榜的人身上。

    那么判断一个排列是否合法,就相当于判断每个人都取最少题数的时候,总题数是否 (leq m)

    记原先的第一名为 (p_0),滚榜顺序为排列 (p_1sim p_n)。显然我们有:

    [b_i = max(b_{i-1},b_{i-1}+a_{p_{i-1}}-a_{p_i}+[p_i>p_{i-1}]) \ sum_{i=1}^n b_i = sum_{i=1}^n(n-i+1)cdot max(0, a_{p_{i-1}}-a_{p_i}+[p_i>p_{i-1}]) ]

    于是我们就可以设计一个状压 DP,设 (f(s,v,l)) 表示以下条件的方案数:

    • 已经用了集合 (s) 中的元素作为排列的前 (|s|) 个元素
    • 当前

    [sum_{i=1}^{lvert s vert}(n-i+1)cdot max(0, a_{p_{i-1}}-a_{p_i}+[p_i>p_{i-1}])=v ]

    • 最后一个元素,即 (p_{|s|}=l)

    转移就再枚举一个 (x in [n]setminus S),转移到

    [fBig(scup {x}, v+(n-i+1)cdot max(0, a_{p_{i-1}}-a_{p_i}+[p_i>p_{i-1}]), xBig) ]

    时间复杂度 (mathcal O(2^nn^2m)),卡卡常数可以过。

    另:https://www.luogu.com.cn/blog/gyh20/solution-p7519
    题解里面有另一种做法,大概是折半搜索。

    code

    #include <bits/stdc++.h>
    
    using std::max; 
    using std::cerr; 
    using std::cout; 
    
    typedef long long s64; 
    
    template <class T>
    inline void read(T &x) {
    	static char ch; 
    	while (!isdigit(ch = getchar()));
    	x = ch - '0'; 
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0'; 
    }
    
    const int MaxN = 15; 
    
    int n, m; 
    int a[MaxN], p[MaxN]; 
    
    const int MaxS = 1 << 13 | 5; 
    const int MaxM = 510; 
    
    s64 f[MaxS][MaxM][MaxN]; 
    
    int main() {
    	freopen("ranklist.in", "r", stdin); 
    	freopen("ranklist.out", "w", stdout); 
    
    	read(n), read(m); 
    
    	a[0] = -1; 
    	for (int i = 1; i <= n; ++i) {
    		read(a[i]); 
    		if (a[i] > a[p[0]]) p[0] = i; 
    	}
    
    	int all = (1 << n) - 1; 
    	for (int i = 1; i <= n; ++i) {
    		int val = max(0, a[p[0]] - a[i] + (i > p[0])) * n; 
    		if (val <= m) {
    			f[1 << (i - 1)][val][i] = 1; 
    		}
    	}
    
    	for (int s = 1; s <= all; ++s) {
    		int cnt = 0, seq[MaxN], nS = 0; 
    		for (int i = 1; i <= n; ++i)
    			if (s >> (i - 1) & 1)
    				++cnt; 
    			else
    				seq[++nS] = i; 
    		
    		for (int v = 0; v <= m; ++v)
    			for (int lst = 1; lst <= n; ++lst)
    				if (f[s][v][lst]) {
    					int val = f[s][v][lst]; 
    					for (int j = 1; j <= nS; ++j) {
    						int i = seq[j]; 
    						int c = max(0, a[lst] - a[i] + (i > lst)) * (n - cnt); 
    
    						if (v + c <= m)
    							f[s | (1 << (i - 1))][v + c][i] += val; 
    					}
    				}
    	}
    
    	s64 res = 0; 
    	for (int v = 0; v <= m; ++v)
    		for (int i = 1; i <= n; ++i)
    			res += f[all][v][i]; 
    	
    	cout << res << '
    '; 
    
    	return 0; 
    }
    

    F. D2T3 支配

    首先我们定义 (x operatorname{dom} y) 当且仅当 (1 o y) 的每条路径都经过了点 (x)。不难发现以下性质:

    • (xoperatorname{dom} y,yoperatorname{dom} zimplies xoperatorname{dom} z)
    • (xoperatorname{dom} y, yoperatorname{dom} ximplies x=y)
    • (xoperatorname{dom} z, yoperatorname{dom} zimplies xoperatorname{dom} y operatorname{lor} yoperatorname{dom} x)

    于是对于一个点 (x(x eq 1))(D_x) 中的点可以根据它们之间的支配关系排成一条链。

    我们总能找到唯一一个 (yin D_xsetminus{x}),使得 (forall zin D_xsetminus{x} , zoperatorname{dom} y),这里的 (y) 就是 (D_xsetminus{x})(|D_y|) 最大的点。将 (y)(x) 连边,我们就会连出一个 (n) 个点的图,每个点入度 (leq 1),也不存在环,而 (1) 又支配所有点,因此一定是一棵有根外向树,我们就称之为支配树

    很容易发现,(D_x) 即这个支配树上 (x) 的祖先集合 (operatorname{anc}(x)) 并上自己,即 (D_x=operatorname{anc}(x)cup {x})

    那么首先建出原图的支配树,这里直接 (mathcal O(n(m+n))) 删去每个点后跑 DFS/BFS 求出受支配集 (D_x) 后建出来。

    (mathrm{fa}_x) 表示 (x) 在支配树上的父亲。

    那么加一条边后,显然 (D_x'subseteq D_x),新的 (D_x') 就是原来 (x) 到根的路径上的一些点。所以如果 (D_x) 改变,那么只有可能是 (mathrm{fa}_x) 改变了或者 (D_{mathrm{fa}_x}) 改变了。

    而如果 (mathrm{fa}_x) 改变了,(x) 子树中的所有点的 (D_x) 都会改变。我们只需要考虑每个点 (x)(mathrm{fa}_x) 是否还支配 (x) 即可。

    具体地,对于每个点 (x),删去 (mathrm{fa}_x) 后从 (1) 开始 DFS/BFS,肯定会有 (1) 无法到达 (x)(因为 (mathrm{fa}_x operatorname{dom} x))。此时,记删去 (mathrm{fa}_x) 后的图是 (G_{x}),我们将当前这张图点分成三类:

    • (1) 开始能够到达的点构成点集 (A_x)
    • (y) 出发能够走到 (x) 的点构成点集 (B_x)
    • 其余点构成点集 (C_x)

    显然,一条连边 ((u,v)) 会使 (mathrm{fa}_x) 不再支配 (x),当且仅当 (uin A_xland vin B_x)

    依次判断即可做到 (mathcal O(n(m+n)+Qn)) 的时间复杂度。

    code

    #include <bits/stdc++.h>
    
    #define foredge(u) for (int e = adj[u], v; v = to[e], e; e = nxt[e])
    
    using std::cerr; 
    using std::vector; 
    
    typedef vector<int> vi; 
    
    template <class T>
    inline void read(T &x) {
    	static char ch; 
    	while (!isdigit(ch = getchar())); 
    	x = ch - '0'; 
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0'; 
    }
    
    template <class T>
    inline void putint(T x) {
    	static char buf[15], *tail = buf; 
    	if (!x) putchar('0'); 
    	else {
    		if (x < 0) putchar('-'), x = ~x + 1; 
    		for (; x; x /= 10) *++tail = x % 10 + '0'; 
    		for (; tail != buf; --tail) putchar(*tail); 
    	}
    }
    
    const int MaxN = 3e3 + 5; 
    const int MaxE = MaxN << 2; 
    
    int n, m, Q;
    
    int par[MaxN]; 
    vi dom[MaxN], adj[MaxN], adjR[MaxN]; 
    
    void bfs(int del) {
    	static int que[MaxN], qr; 
    	static bool vis[MaxN]; 
    
    	for (int i = 1; i <= n; ++i) {
    		vis[i] = false; 
    	}
    
    	if (del != 1) {
    		vis[que[qr = 1] = 1] = true; 
    		for (int i = 1; i <= qr; ++i) {
    			int u = que[i]; 
    			for (int ei = 0; ei < (int)adj[u].size(); ++ei) {
    				int v = adj[u][ei]; 
    				if (!vis[v] && v != del) {
    					vis[que[++qr] = v] = true; 
    				}
    			}
    		}
    	}
    	
    	for (int i = 1; i <= n; ++i)
    		if (!vis[i]) dom[i].push_back(del); 
    }
    
    int dfn[MaxN]; 
    int tag[MaxN][MaxN]; 
    
    void makeTag(int del, int st) {
    	if (del == 1) return; 
    	
    	static int que[MaxN], qr; 
    	static bool vis[MaxN]; 
    
    	for (int i = 1; i <= n; ++i) vis[i] = false; 
    
    	vis[que[qr = 1] = 1] = true; 
    	for (int i = 1; i <= qr; ++i) {
    		int u = que[i]; 
    		tag[u][dfn[st]] = 1; 
    		for (int ei = 0; ei < (int)adj[u].size(); ++ei) {
    			int v = adj[u][ei]; 
    			if (!vis[v] && v != del) {
    				vis[que[++qr] = v] = true; 
    			}
    		}
    	}
    
    	for (int i = 1; i <= n; ++i) vis[i] = false; 
    	
    	vis[que[qr = 1] = st] = true; 
    	for (int i = 1; i <= qr; ++i) {
    		int u = que[i]; 
    		tag[u][dfn[st]] = 2; 
    		for (int ei = 0; ei < (int)adjR[u].size(); ++ei) {
    			int v = adjR[u][ei]; 
    			if (!vis[v] && v != del) {
    				vis[que[++qr] = v] = true; 
    			}
    		}
    	}
    }
    
    vi adjT[MaxN]; 
    int dfsClock, idx[MaxN], sze[MaxN]; 
    
    void dfsInit(int u) {
    	idx[dfn[u] = ++dfsClock] = u; 
    	for (int ei = 0; ei < (int)adjT[u].size(); ++ei) {
    		int v = adjT[u][ei]; 
    		dfsInit(v); 
    	}
    }
    
    int main() {
    	freopen("dominator.in", "r", stdin); 
    	freopen("dominator.out", "w", stdout); 
    
    	read(n), read(m), read(Q); 
    	for (int i = 1; i <= m; ++i) {
    		int u, v; 
    		read(u), read(v); 
    		adj[u].push_back(v); 
    		adjR[v].push_back(u); 
    	}
    
    	for (int i = 1; i <= n; ++i) {
    		bfs(i); 
    	}
    
    	for (int u = 1; u <= n; ++u) {
    		for (int i = 0; i < (int)dom[u].size(); ++i) {
    			int v = dom[u][i]; 
    			if (dom[v].size() == dom[u].size() - 1) {
    				par[u] = v; 
    				break; 
    			}
    		}
    		if (u > 1) adjT[par[u]].push_back(u); 
    
    		// cerr << u << ':' << par[u] << '
    '; 
    	}
    
    	dfsInit(1); 
    	for (int u = 2; u <= n; ++u) {
    		makeTag(par[u], u); 
    	}
    
    	while (Q--) {
    		int x, y; 
    		read(x), read(y); 
    
    		static bool chg[MaxN]; 
    		int ans = 0; 
    
    		for (int i = 2; i <= n; ++i) {
    			int u = idx[i]; 
    			chg[u] = chg[par[u]] | (tag[x][i] == 1 && tag[y][i] == 2); 
    			ans += chg[u]; 
    		}
    
    		putint(ans), putchar('
    '); 
    	}
    
    	return 0; 
    }
    
    

    G. B卷 D1T1 数对

    略。

    H. B卷 D2T1 取模

    先将 (a_1sim a_n) 全部排序。

    假设当前的模数是 (a_k),记 (b_i=a_i mod a_k)

    假设选出的两个数为 (i,j(i eq j,i eq k,j eq k))

    • (b_i + b_j geq a_k),这个时候我们只要找 (b_i,b_j) 最大的两个相加计算即可。
    • (b_i + b_j < a_k),这个时候我们只要对于每个 (b_i),找到最大的满足 (b_i + b_j < a_k)(b_j) 即可,排序后用双指针扫描。

    但是如果对于每个 (a_k) 都这么做的话,时间复杂度显然是 (mathcal O(n^2 log n)) 的。

    但是我们只需要从大到小枚举 (a_k),并且储存当前的最优答案 (mathrm{ans}),在 (a_k - 1 > mathrm{ans}) 的时候才去计算,否则就结束程序。

    显然需要计算的是一段后缀。假设当前考虑到了 (a_k),那么

    [mathrm{ans} < a_k leq a_{k+1} leq cdots leq a_n ]

    显然

    [mathrm{ans} geq (a_i + a_{i+1}) mod a_{i+2}geq a_i + a_{i+1} - a_{i+2}(a_i<a_{i+2}) ]

    注意这个分析在相同的 (a_i) 不超过两个的时候才是成立的,否则时间复杂度就会可能是错的

    因此

    [a_{i+2}-mathrm{ans} geq (a_i-mathrm{ans}) + (a_{i-1} - mathrm{ans}) ]

    注意到 (a_i - mathrm{ans} > 0),因此 (a_i-mathrm{ans}=mathcal O(V)) 是斐波那契数列级别的增长((V) 表示 (a) 的最大值),所以需要枚举的个数是 (mathcal O(log V)) 的。

    因此时间复杂度为 (mathcal O(n log n log V))

    #include <bits/stdc++.h>
    
    template <class T>
    inline void read(T &x) {
    	static char ch;
    	while (!isdigit(ch = getchar())); 
    	x = ch - '0'; 
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0'; 
    }
    
    template <class T>
    inline void relax(T &x, const T &y) {
    	if (x < y) x = y; 
    }
    
    const int MaxN = 2e5 + 5; 
    
    int ans; 
    int n, a[MaxN]; 
    
    void solve(int p, int del) {
    	static int b[MaxN], m; 
    
    	m = 0; 
    	for (int i = 1; i <= n; ++i) {
    		if (i != del) b[++m] = a[i] % p; 
    	}
    
    	std::sort(b + 1, b + m + 1); 
    
    	relax(ans, (b[m - 1] + b[m]) % p); 
    
    	for (int i = 1, lst = m; i <= m; ++i) {
    		while (lst > i && b[lst] + b[i] >= p) --lst; 
    		if (lst <= i) return; 
    
    		relax(ans, b[i] + b[lst]); 
    	}
    }
    
    int main() {
    	freopen("mod.in", "r", stdin); 
    	freopen("mod.out", "w", stdout); 
    
    	read(n);
    	for (int i = 1; i <= n; ++i) {
    		read(a[i]); 
    	}
    	std::sort(a + 1, a + n + 1); 
    
    	ans = 0; 
    	for (int i = n; i >= 1; --i)
    		if (a[i] - 1 > ans && (i == 1 || a[i] != a[i - 1]))
    			solve(a[i], i); 
    
    	std::cout << ans << '
    '; 
    	return 0; 
    }
    
  • 相关阅读:
    安装最新版本的zabbix
    原 linux添加虚拟ip(手动vip和keepalived方式)
    zzuli OJ 1129: 第几天
    WIN内核线程池函数
    Java中利用Math.random()产生服从泊松分布的随机数
    机房重构所遇问题&quot;未能载入文件或程序集“DAL”或它的某一个依赖项。系统找不到指定的文件&quot;的解决的方法集锦
    二路插入排序
    iOS开发之地图与定位
    2015-07-30Java 错题
    bootstrap, boosting, bagging
  • 原文地址:https://www.cnblogs.com/cyx0406/p/14749959.html
Copyright © 2020-2023  润新知