CSP-S开小灶1

A. ZZH的游戏

假设我们知道答案 \(ans\)

我们每次要在两棵树上尝试走到编号尽可能小的点,这样我们能走到的范围单调递增

二分答案会多个 \(log\), 我们考虑另一种方法

初始\(ans = s + t\)

然后两棵树扩展到不能动为止,这个时候我们让 \(ans++\),继续之前的过程,直到两棵树都到 \(1\)为止

具体过程可以用桶排优化到线性,但是我太菜了,只会优先队列多个\(log\)

upd:其实完全没有排序,直接每次对之前不能访问但能到达的最小的\(dfs\)下去就行了,写成我这样的\(bfs\)就是完全的负优化

code
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<map>
#include<set>
using namespace std;

int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
const int maxn = 1000005;
int n;
struct tree{
	struct edge{
		int to, net;
	}e[maxn << 1 | 1];
	int head[maxn], tot;
	void add(int u, int v){
		e[++tot].net = head[u];
		head[u] = tot;
		e[tot].to = v;
	}
	bool vis[maxn];
	int mi = maxn + maxn + maxn;
	priority_queue<int, vector<int>, greater<int> >q;
	void clear(){
		for(int i = 1; i <= n; ++i)head[i] = 0;
		for(int i = 1; i <= n; ++i)vis[i] = 0;
		tot = 0; mi = maxn + maxn + maxn;
		while(!q.empty())q.pop();
	}
	void built(){
		for(int i = 1; i < n; ++i){
			int u = read(), v = read();
			add(u, v); add(v, u);
		}
	}
	bool expand(int mx){
		bool flag = 0;
		while(!q.empty()){
			int x = q.top();
			if(x > mx)break;
			q.pop(); vis[x] = 1; flag = 1; mi = min(mi, x);
			for(int i = head[x]; i; i = e[i].net){
				int v = e[i].to; if(vis[v])continue;
				q.push(v);
			}

		}
		return flag;
	}
	void set(int x){mi = x; q.push(x);}
}t1, t2;
int s, t;
int solve(){
	n = read(); t1.built(); t2.built();
	s = read(); t = read();
	int ans = s + t;
	t1.set(s); t2.set(t);
	t1.expand(s); t2.expand(t);
	while(1){
		bool f1 = 1, f2 = 1;
		while(f1 || f2){
			if(t1.mi == 1 && t2.mi == 1)return ans;
			f1 = t1.expand(ans - t2.mi);
			f2 = t2.expand(ans - t1.mi);
		}
		++ans;
	}
}
int main(){
	int t = read();
	for(int ask = 1; ask <= t; ++ask){
		printf("%d\n",solve());
		t1.clear(); t2.clear();
	}
	return 0;
}

B. ZZH与背包

考场打了个搜索 + 剪枝,大样例没跑过, 交上去切了 ???

正解不是很理解,但是有近似正解的做法

分成两组,\(\text{meet in middle}\)

左半边枚举,右半边双指针

upd:关于正解做法,一部分物品处理同上,但是排序用归并降为线性,考虑对询问离线并排序,考虑剩下的物品每个选或者不选,不选询问仍是原值,选的话相当于询问减去 \(v\),这样一个询问变成了两个询问,而且是两个有序数列,可以进行归并,这样把询问拆分完之后,仍然可以用双指针处理之前的那部分物品,最后合并询问即可,通过调整不同方式处理的物品数量复杂度可以达到 \(\sqrt {q2^n}\)

code
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<map>
#include<set>

using namespace std;

typedef long long ll;
ll read(){
	ll x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
int n, q, v[45], m1, m2, n1, n2;
ll a[(1 << 20) + 5], b[(1 << 20) + 5];
ll solve(ll mv){
	ll ans = 0; int r = 0;
	for(int l = m1 - 1; l >= 0; --l){
		while(r < m2 && a[l] + b[r] <= mv) ++r;
		ans += r;
	}
	return ans;
}
int lg[(1 << 20) + 5];
int main(){
	n = read(), q = read();
	n1 = n / 2, n2 = n - n1;
	for(int i = 1; i <= n1; ++i)v[i] = read();
	m1 = 1 << n1;
	lg[1] = 0;
	for(int i = 1; i <= n2; ++i)lg[1 << i] = i; 
	for(register int i = 0; i < m1; ++i){
		int now = i;
		for(register int j = now; j; j -= j & -j)a[i] += v[lg[j & -j] + 1];
	}
	sort(a + 1, a + m1);
	for(int i = 1; i <= n2; ++i)v[i] = read();
	m2 = 1 << n2;
	for(register int i = 0; i < m2; ++i){
		int now = i;
		for(register int j = now; j; j -= j & -j)b[i] += v[lg[j & -j] + 1];
	}
	sort(b + 1, b + m2);
	for(int i = 1; i <= q; ++i){
		ll l = read(), r = read();
		printf("%lld\n",solve(r) - solve(l - 1));
	}
	return 0;
}
posted @ 2022-09-05 18:58  Chen_jr  阅读(50)  评论(0编辑  收藏  举报