2023冲刺国赛模拟2

A. 树

如果只有一个初始点

那么设 fx 表示 x 子树全部被占领的最短时间

转移将儿子的 f 从大到小排序, fx=max(fv+rkv)

两个特殊点之间,一定存在一条边实际上没有用,而且答案显然对一个特殊点有单调性

三分?因为不严格单峰,所以不行。

但是最小值一定在两边答案最接近附近取到,改为二分求两边的差值

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
const int maxn = 5e5 + 55;
int n, a, b;
vector<int>g[maxn], chain, tmp[maxn];
bool is[maxn];
int mxd[maxn], fa[maxn], f[maxn];

void get_chain(int x){for(int v : g[x])if(v != fa[x]){fa[v] = x; get_chain(v);}}

void dfs(int x, int fa){ 
	tmp[x].clear();
	for(int v : g[x])if(v != fa && !is[v]){
		dfs(v, x); tmp[v].clear();
		tmp[x].push_back(f[v]);
	}
	f[x] = 1; 
	sort(tmp[x].begin(), tmp[x].end());
	reverse(tmp[x].begin(), tmp[x].end());
	for(int i = 0; i < tmp[x].size(); ++i){
		if(tmp[x][i] + i + 1 >= f[x]){
			f[x] = tmp[x][i] + i + 1;
			mxd[x] = tmp[x][i];
		}
	}
}
int calc(int i){
	int resl = f[chain[i - 1]], resr = f[chain[i]];
	for(int j = i - 2; j >= 0; --j){
		int v = chain[j];
		resl = max(f[v] + (resl >= mxd[v]), resl + 1);
	}
	for(int j = i + 1; j < chain.size(); ++j){
		int v = chain[j];
		resr = max(f[v] + (resr >= mxd[v]), resr + 1);
	}
	return max(resl, resr) - 1;
}
int query(int i){
	int resl = f[chain[i - 1]], resr = f[chain[i]];
	for(int j = i - 2; j >= 0; --j){
		int v = chain[j];
		resl = max(f[v] + (resl >= mxd[v]), resl + 1);
	}
	for(int j = i + 1; j < chain.size(); ++j){
		int v = chain[j];
		resr = max(f[v] + (resr >= mxd[v]), resr + 1);
	}
	return resl - resr;
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	n = read(), a = read(), b = read();
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		g[u].push_back(v); g[v].push_back(u);
	}
	get_chain(a);
	int now = b;
	while(now){
		is[now] = true;
		chain.push_back(now);
		now = fa[now];
	}
	for(int v : chain)dfs(v, 0);
	int l = 1, r = chain.size() - 1;
	while(r - l >= 2){
		int mid = (l + r) >> 1, val = query(mid);
		if(val == 0)l = r = mid;
		else if(val > 0)r = mid;
		else l = mid;
	}
	int ans = INT_MAX;
	for(int i = l; i <= r; ++i)ans = min(ans, calc(i));
	printf("%d\n",ans);
	return 0;
}

B. 最小生成树

考虑给树边确定好大小关系,那么非树边需要大于其连接的两个点树上路径的最大值,可以唯一确定

那么这个限制关系就是一个 Dag

有一个长度为 n1 的链表示树边,非树边挂在下面

那么问题可以转化成,有若干黑白球,白球任意放,黑球只能放最前面的方案数

放球的顺序是 wn1 个白球,1 个黑球,wn2 个白球,1 个黑球.....

记录四元组(n,b,c,s)表示当前有n个球,b个黑球,方案数为c,黑球位置和为s。在加入球的过程中更新四元组。

  • 加入一个白球:

(n,b,c,s)(n+1,b,(n+1)c,(n+2)s)

  • 加入wi个白球:

(n,b,c,s)(n+wi,b,(n+wi)!/n!c,(n+wi+1)!/(n+1)!s)

  • 加入一个黑球:

(n,b,c,s)(n+1,b+1,c,s+(b+1)c)

那么通过枚举前n1条边的权值大小顺序,可以得到一个O(n!)的做法

使用状压可以 n2n, 问题在于求 w

每次枚举一条边加上时,需要的 w是能和未加入的边集和当前边组成环的,

那么如果 as 表示能与 s 组成环的集合大小那么 fs|2ifs 即为 w

那么这个东西可以预处理每条非树边对应的树边的集合,做子集和卷积即可

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}
const int mod = 1e9 + 7, mx = 1 << 20, maxn = 405;
int qpow(int x, int y){
	int ans = 1;
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
int n, m, fac[maxn], ifac[maxn];
void add(int &x, int y){x += y; if(x >= mod)x -= mod;}
int f[mx], g[mx], h[mx], a[mx];
int fa[25], dep[25], id[25];
vector<pii>e[25];
void dfs(int x){
	for(pii v : e[x])if(v.first != fa[x]){
		fa[v.first] = x; id[v.first] = v.second; 
		dep[v.first] = dep[x] + 1;
		dfs(v.first);
	}
}
int main(){
	freopen("mst.in","r",stdin);
	freopen("mst.out","w",stdout);
	n = read(), m = read();
	fac[0] = ifac[0] = 1; for(int i = 1; i <= 200; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
	ifac[200] = qpow(fac[200], mod - 2);
	for(int i = 199; i >= 1; --i)ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mod;
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		e[u].push_back(pii(v, i));
		e[v].push_back(pii(u, i));
	}
	dfs(1);
	for(int i = n; i <= m; ++i){
		int u = read(), v = read();
		int s = 0;
		while(u != v){
			if(dep[u] > dep[v])s |= 1 << id[u] - 1, u = fa[u];
			else s |= 1 << id[v] - 1, v = fa[v];
		}
		++a[s];
	}
	for(int l = 2, hl = 1; l <= (1 << n - 1); hl = l, l <<= 1)
		for(int i = 0; i < (1 << n - 1); i += l)
			for(int j = i; j < i + hl; ++j)
				add(a[j + hl], a[j]);
	g[0] = 1;
	for(int s = 0; s < (1 << n - 1); ++s){
		int cnt = __builtin_popcount(s);
		for(int i = 1; i < n; ++i)if(!((s >> i - 1) & 1)){
			int w = a[((1 << n - 1) - 1) ^ s] - a[((1 << n - 1) - 1) ^ (s | (1 << i - 1))];
			int ng = 1ll * g[s] * fac[h[s] + w] % mod * ifac[h[s]] % mod;
			add(g[s | (1 << i - 1)], ng);
			add(f[s | (1 << i - 1)], (1ll * f[s] * fac[h[s] + w + 1] % mod * ifac[h[s] + 1] % mod + 1ll * (cnt + 1) * ng) % mod);
            h[s | (1 << i - 1)] = h[s] + w + 1;
		}
	}
	printf("%d\n", f[(1 << n - 1) - 1]);
	return 0;
}

C. 矩阵

行和列相对独立,分开维护

每次查询到如果行列都是原序列上的值可以直接输出

而对于修改过的值,其值只用关心后修改的那部分,找到当前第 k 大的点,然后查询在修改的那个时刻该点的排名即为答案

用到了历史信息,所以用可持久化平衡树维护

查询历史版本某个点的排名需要一个权值,用替罪羊树维护,每次在历史版本上查询即可

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; bool f = false; char c = getchar();
	while(!isdigit(c))f = c == '-', c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return f ? -x : x;
}
const int maxn = 4e5 + 55;
mt19937 rd((ull)&maxn);
int sint(){return uniform_int_distribution<>(INT_MIN, INT_MAX)(rd);}
int n, p, q;
struct data{
	double val[maxn];
	struct scapegoat_tree{
		const double alpha = 0.75;
		struct node{
			int l, r, si, id;
		}t[maxn];
		int cnt, st[maxn], top;
		double *val;
		void pre(double *p){val = p;}
		void push_up(int x){t[x].si = t[t[x].l].si + t[t[x].r].si + 1;}
		void dfs(int x){
			if(t[x].l)dfs(t[x].l);
			st[++top] = x;
			if(t[x].r)dfs(t[x].r);
		}
		int build(int l, int r, double ll, double rr){
			if(l > r)return 0;
			int mid = (l + r) >> 1, x = st[mid]; val[t[x].id] = (ll + rr) / 2; 
			t[x].l = build(l, mid - 1, ll, val[t[x].id]);
			t[x].r = build(mid + 1, r, val[t[x].id], rr);
			push_up(x); return x;
		}
		void rebuild(int &x, double l, double r){
			if(!x)return; top = 0; dfs(x);
			x = build(1, top, l, r);
		}
		bool check(int x){return alpha * t[x].si <= max(t[t[x].l].si, t[t[x].r].si);}
		void insert(int &x, int k, int id, double l, double r){
			double mid = (l + r) / 2;
			if(!x){
				x = ++cnt;
				t[x].si = 1; 
				t[x].id = id;
				val[id] = mid;
				return;
			}
			if(t[t[x].l].si + 1 >= k)insert(t[x].l, k, id, l, mid);
			else insert(t[x].r, k - t[t[x].l].si - 1, id, mid, r);
			push_up(x); if(check(x))rebuild(x, l, r);
		}
	}st;
	struct FHQ_Treap{
		double *val;
		void pre(double *p){val = p;}
		struct node{
			int l, r, si, id, key;
		}t[maxn * 30];
		int cnt;
		int New(int id){++cnt; t[cnt].si = 1; t[cnt].id = id; t[cnt].key = sint(); return cnt;}
		void push_up(int x){t[x].si = t[t[x].l].si + t[t[x].r].si + 1;}
		int merge(int x, int y){
			if(!x || !y)return x | y;
			int now = ++cnt; 
			if(t[x].key < t[y].key){
				t[now] = t[x];
				t[now].r = merge(t[x].r, y);
			}else{
				t[now] = t[y];
				t[now].l = merge(x, t[y].l);
			}
			push_up(now); return now;
		}
		void split(int x, int k, int &l, int &r){
			if(!x){l = r = 0; return;}
			int now = ++cnt; t[now] = t[x];
			if(t[t[x].l].si + 1 <= k){
				split(t[now].r, k - t[t[x].l].si - 1, t[now].r, r);
				l = now;
			}else{
				split(t[now].l, k, l, t[now].l);
				r = now;
			}
			push_up(now);
		}
		void insert(int &rt, int k, int id){
			int l = 0, r = 0; split(rt, k - 1, l, r);
			rt = merge(merge(l, New(id)), r);
		}
		int kth(int x, int k){
			while(true){
				if(t[t[x].l].si + 1 == k)return t[x].id;
				if(t[t[x].l].si >= k)x = t[x].l;
				else k -= t[t[x].l].si + 1, x = t[x].r;
			}
		}
		int rank(int x, int k){
			int ans = 0;
			while(true){
				if(val[t[x].id] == val[k])return ans + t[t[x].l].si + 1;
				if(val[t[x].id] < val[k])ans += t[t[x].l].si + 1, x = t[x].r;
				else x = t[x].l;
			}
			return ans;
		}
	}ft;
	data(){st.pre(val); ft.pre(val);}
	int rt[maxn], srt;
	void insert(int id, int k, int ver){
		st.insert(srt, k, id, 0, 10000);
		ft.insert(rt[ver], k, id);
	}
}t1, t2;
const int mv = 1e5 + 1;
int main(){
	freopen("matrix.in","r",stdin);
	freopen("matrix.out","w",stdout);
	n = read(); int cnt = 2e5 + 1;
	for(int i = 1; i <= cnt; ++i)t1.insert(i, i, 0), t2.insert(i, i, 0);
	for(int i = 1; i <= n; ++i){
		int op = read(), x = read();
		t1.rt[i] = t1.rt[i - 1];
		t2.rt[i] = t2.rt[i - 1];
		if(op == 1)t1.insert(cnt + i, x + p + q + mv, i);
		else if(op == 2)t2.insert(cnt + i, x + p + q + mv, i);
		else{
			int y = read();
			x += p + q; y += p - q;
			int a = t2.ft.kth(t2.rt[i], x + mv), b = t1.ft.kth(t1.rt[i], y + mv);
			if(a <= cnt && b <= cnt)p = a - mv, q = b - mv;
			else if(a > b){
				p = a - cnt; q = t1.ft.rank(t1.rt[a - cnt], b) - mv;
			}else{
				p = t2.ft.rank(t2.rt[b - cnt], a) - mv; q = b - cnt;
			}
			printf("%d %d\n",p, q);
		}
	}
	return 0;
}
posted @   Chen_jr  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示