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;
}