2019杭电多校第三场

2019杭电多校第三场

1002. Blow up the city

upsloved

有一个DAG,出度为(0)的点是指挥中心,(q)次询问,每次给出两个点,这两个点存有关键物资,你可以炸掉一个点和它的邻接边,使得这两个点中任意一个点的物资不能到达指挥中心(有一个点不能到达任意一个指挥中心即可),求方案数,询问独立

并不会支配树,比赛的时候想到了这个东西的概念,但是不会实现,赛后学习一波

把图反过了,建一个额外的起点指向指挥中心,求出支配树,每次询问u,v就只要计算u,v以及它们的LCA的深度就好了

#include <bits/stdc++.h>
using LL = long long;
using namespace std;

const int N = 200010;
struct Node{int to,next;};
int T, n, m, q, u, v, dfn[N], clo, rev[N], f[N], semi[N], idom[N], deg[N], dep[N], fa[N][18];

struct Graph{
	Node E[N << 1]; int head[N], tot;
	inline void clear(){
		tot = 0;
		for(int i = 0; i <= n; ++i) head[i] = -1;
	}
	inline void link(int u, int v){
		E[tot] = (Node){v, head[u]}; head[u] = tot++;
	}
}pre, nxt, dom;

struct uset{
	int fa[N], Mi[N];
	inline void init(){
		for(int i = 1; i <= n; ++i)
			fa[i] = Mi[i] = semi[i] = i;
	}
	inline int find(int x){
		if(fa[x] == x) return x;
		int fx = fa[x], y = find(fa[x]);
		if(dfn[semi[Mi[fx]]] < dfn[semi[Mi[x]]]) Mi[x] = Mi[fx];
		return fa[x] = y;
	}
}uset;

inline void tarjan(int x) {
	dfn[x] = ++clo; rev[clo] = x;
	for(int e = nxt.head[x]; ~e; e = nxt.E[e].next){
		if(!dfn[nxt.E[e].to])
			f[nxt.E[e].to] = x, tarjan(nxt.E[e].to);
	}
}

inline void dfs(int x, int pre, int depth) {
	dep[x] = depth;
	fa[x][0] = pre;
	for(int i = 1; i < 18; ++i)
		fa[x][i] = fa[fa[x][i - 1]][i - 1];
	for(int e = dom.head[x]; ~e; e = dom.E[e].next)
		dfs(dom.E[e].to, x, depth + 1);
}

inline int LCA(int u, int v) {
	if(dep[u] < dep[v]) swap(u, v);
	for(int i = 17; ~i; --i) {
		if(dep[fa[u][i]] >= dep[v])
			u = fa[u][i];
	}
	if(u == v) return u;
	for(int i = 17; ~i; --i) {
		if(fa[u][i] != fa[v][i])
			u = fa[u][i], v = fa[v][i];
	}
	return fa[u][0];
}

inline void calc(){
	for(int i = n; i >= 2; --i){
		int y = rev[i], tmp = n;
		for(int e = pre.head[y]; ~e; e = pre.E[e].next){
			int x = pre.E[e].to; if(!dfn[x]) continue;
			if(dfn[x] < dfn[y]) tmp = min(tmp, dfn[x]);
			else uset.find(x), tmp = min(tmp, dfn[semi[uset.Mi[x]]]);
		}
		semi[y] = rev[tmp]; uset.fa[y] = f[y];
		dom.link(semi[y], y);
		y = rev[i - 1];
		for(int e = dom.head[y]; ~e; e = dom.E[e].next){
			int x = dom.E[e].to; uset.find(x);
			if(semi[uset.Mi[x]] == y) idom[x] = y;
			else idom[x] = uset.Mi[x];
		}
	}
	for(int i = 2; i <= n; ++i){
		int x = rev[i];
		if(idom[x] != semi[x])
			idom[x] = idom[idom[x]];
	}
	dom.clear();
	for(int i = 1; i < n; ++i)
		dom.link(idom[i], i);
	dfs(n, 0, 0);
}

int main() {
	scanf("%d", &T);
	while(T--) {
		scanf("%d%d", &n, &m);
		clo = 0; n++; dep[0] = -1;
		for(int i = 1; i <= n; ++i)
			deg[i] = dfn[i] = rev[i] = semi[i] = idom[i] = f[i] = 0;
		pre.clear(); nxt.clear(); dom.clear(); uset.init();
		for(int i = 1; i <= m; ++i){
			scanf("%d%d", &u, &v);
			pre.link(u, v);
			nxt.link(v, u);
			deg[u]++;
		}
		for(int i = 1; i < n; ++i) {
			if(deg[i] == 0) {
				nxt.link(n, i);
				pre.link(i, n);
			}
		}
		tarjan(n);
		calc();
		scanf("%d", &q);
		while(q--) {
			scanf("%d%d", &u, &v);
			printf("%d
", dep[u] + dep[v] - dep[LCA(u, v)]);
		}
	}
	return 0;
}

1004. Distribution of books

upsloved

有一个长为(n)的数列(A), 你需要将找(k)个飞空不相交区间,这些区间并起来是(A)的一个前缀区间,使得这(k)个区间每个区间的和的最大值最小

二分答案,线段树优化dp套路,代码不放了

1006. Fansblog

solved at 00:54

队友做的,素数分布,威尔逊定理,int128

1007. Find the answer

solved at 01:24(+3)

离散化之后线段树维护区间和以及区间数的个数

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;

struct node {
	int val, id;
	bool operator<(const node &rhs) const {
		return val < rhs.val;
	}
}a[N];

int rnk[N], T, n, ans[N], m, sum, mn, b[N];

long long tree[N << 2];
int cnt[N << 2];

void pushup(int rt) {
	tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];
	cnt[rt] = cnt[rt << 1] + cnt[rt << 1 | 1];
}

void build(int rt, int l, int r) {
	tree[rt] = 0; cnt[rt] = 0;
	if(l == r) return;
	int mid = l + r >> 1;
	build(rt << 1, l, mid);
	build(rt << 1 | 1, mid + 1, r);
}

void update(int rt, int l, int r, int pos, int val) {
	if(l == r) {tree[rt] += val, cnt[rt]++; return;}
	int mid = l + r >> 1;
	if(pos <= mid) update(rt << 1, l, mid, pos, val);
	else update(rt << 1 | 1, mid + 1, r, pos, val);
	pushup(rt);
}

int query(int rt, int l, int r, long long val) {
	if(l == r) return tree[rt] <= val ? cnt[rt] : 0;
	int mid = l + r >> 1;
	if(tree[rt << 1] <= val) return cnt[rt << 1] + query(rt << 1 | 1, mid + 1, r, val - tree[rt << 1]);
	else return query(rt << 1, l, mid, val); 
}

int main() {
	read(T);
	while(T--) {
		sum = ans[0] = 0;
		read(n); read(m);
		for(int i = 1; i <= n; ++i) {
			read(a[i].val);
			b[i] = a[i].val;
			a[i].id = i;
		}
		sort(a + 1, a + n + 1);
		for(int i = 1; i <= n; ++i)
			rnk[a[i].id] = i;
		build(1, 1, n);
		for(int i = 1; i <= n; ++i) {
			ans[i] = i - 1 - query(1, 1, n, m - b[i]);
			update(1, 1, n, rnk[i], b[i]);
		}
		for(int i = 1; i <= n; ++i)
			printf("%d ", ans[i]);
		puts("");
	}
	return 0;
}


1009. K subsequence

solved at 04:42(+14)

有一个长为(n)的数列,你需要找出(k)个不相交的非降子序列使得选出的数和最大(1<=n<=2000, 1<=k<=10)

最小费用最大流经典题,拆点建图,每个点(i)拆成(i_1)(i_2)(i_1)(i_2)建容量为(1),费用为(-a_i)的边,(s)(i_1)建容量为(1),费用为(0)的边,(i_2)(t)建容量为(1),费用为(0)的边,如果(a[i]<=a[j])(i<j)(i_2)(j_1)建容量为(1),费用为(0)的边,最后(ss)(s)建容量为(k),费用为(0)的边,(ss)(t)(MCMF),最小费用取相反数就是答案

1011. Squrirrel

树型dp,待补

原文地址:https://www.cnblogs.com/tusikalanse/p/11273174.html