A. 跳石头
首先我们观察一下青蛙是怎么跳的:对于一个断点 ((i,i+1)),如果有 (x) 只青蛙往左跳,那么一定有 (x) 只青蛙往右跳,所以 (a_i) 一定是偶数。
并且我们发现从 (a_i) 到 (a_{i+1}),变化的只可能是 (i+1) 这一个青蛙,也就是有 (|a_i-a_{i+1}| in {0,2,-2})。
我们考虑 (a_i) 到 (a_{i+1}) 的变化:如果是 (0) 说明一定是 (i) 跳到自己,如果多了 (2) 说明 (i) 往后跳,少了 (2) 是往前跳。
所以我们可以得出每个点往哪里跳,然后用类似括号序列的方式配对就行了:遇到往右的加入栈,往左的就随便取出一个。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 2e5 + 5;
int n,a[MAXN];
int ans[MAXN];
int main(){
scanf("%d",&n);
FOR(i,1,n-1) scanf("%d",a+i);
bool flag = 1;
FOR(i,1,n){
flag &= !(a[i]&1);
flag &= (std::abs(a[i]-a[i-1]) == 2 || a[i] == a[i-1]);
}
if(!flag){
puts("No");
return 0;
}
std::vector<int> S;
FOR(i,1,n){
if(a[i] == a[i-1]+2){// 左括号
S.pb(i);
}
else if(a[i] == a[i-1]){
ans[i] = i;
}
else{ // 右括号
if(S.empty()){
puts("No");
return 0;
}
ans[S.back()] = i;
ans[i] = S.back();
S.pop_back();
}
}
if(!S.empty()){
puts("No");
return 0;
}
puts("Yes");
FOR(i,1,n) printf("%d%c",ans[i],"
"[i==n]);
return 0;
}
/*
1. a[i] 都是偶数 并且正好一半往左一半往右
2. 考虑 a[i] 和 a[i+1]:如果中间不动 a[i]==a[i+1],如果中间往右 a[i+1] = a[i]+2,如果中间往左 a[i+1] = a[i]-2
往右必然要有一个往左 所以遇到往右的可以看成左括号 往左的可以看成右括号 就行了
*/
B. 树
先把颜色相同的缩成一个树,感性理解操作肯定是每次操作同一个联通块,让他的影响不断增大。我们设第一次操作的点是 (x) ,以 (x) 为根建树,发现操作最大深度次就能完成。所以我们要让最大深度尽量小,就是直径的长度除以二。
稍微注意下处理重边即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 5e5 + 5;
int n,a[MAXN];
std::vector<int> G[MAXN],T[MAXN];
bool vis[MAXN];
int f[MAXN];
inline int find(int x){
return x == f[x] ? x : f[x] = find(f[x]);
}
inline void merge(int x,int y){
x = find(x);y = find(y);
if(x == y) return;
f[x] = y;
}
inline void dfs(int v){
vis[v] = 1;
for(auto x:G[v]){
if(vis[x]) continue;
if(a[x] != a[v]) continue;
merge(v,x);
dfs(x);
}
}
int g[MAXN][2],ans;
inline void dfs2(int v,int fa=0){
for(auto x:T[v]){
if(x == fa) continue;
dfs2(x,v);
if(g[v][0] < g[x][0]+1){
g[v][1] = g[v][0];
g[v][0] = g[x][0]+1;
}
else if(g[v][1] < g[x][0]+1){
g[v][1] = g[x][0]+1;
}
}
ans = std::max(ans,g[v][0]+g[v][1]);
}
std::map<P,int> S;
int main(){
scanf("%d",&n);FOR(i,1,n) scanf("%d",a+i),f[i] = i;
FOR(i,2,n){
int u,v;scanf("%d%d",&u,&v);G[u].pb(v);G[v].pb(u);
}
FOR(i,1,n){
if(vis[i]) continue;
dfs(i);
}
FOR(v,1,n){
for(auto x:G[v]){
if(find(x) != find(v)){
int a = find(x),b = find(v);
if(a > b) std::swap(a,b);
S[MP(a,b)] = 1;
}
}
}
for(auto x:S){
T[x.fi.fi].pb(x.fi.se);
T[x.fi.se].pb(x.fi.fi);
}
dfs2(find(1));
printf("%d
",(ans+1)/2);
return 0;
}
C. 背包
如果没有背包的限制,那么答案就是按照重量从大到小排序,求价值的 LDS。
我们按照重量从大到小排序,那么位置 (i) 我们能得出最多能选几个物品,设 (f_i) 表示考虑了前 (i) 个的答案,树状数组优化转移后特判一下能选几个物品的数量限制就行了。也就是 (f_i = min(f_i,lim_i))。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 2e5 + 5;
struct BIT{
#define lowbit(x) ((x)&(-(x)))
int tree[MAXN];
inline void add(int pos,int x){
while(pos){
tree[pos] = std::max(tree[pos],x);
pos -= lowbit(pos);
}
}
inline int query(int pos){
int res = 0;
while(pos < MAXN){
res = std::max(res,tree[pos]);
pos += lowbit(pos);
}
return res;
}
}bit;
int n;
P a[MAXN];
int lim[MAXN],w[MAXN],m;
inline void Solve(){
scanf("%d",&n);std::vector<int> S;CLR(bit.tree,0);
FOR(i,1,n) scanf("%d%d",&a[i].fi,&a[i].se),S.pb(a[i].se);
std::sort(all(S));S.erase(std::unique(all(S)),S.end());
FOR(i,1,n) a[i].se = std::lower_bound(all(S),a[i].se)-S.begin()+1;
std::sort(a+1,a+n+1,[&](const P &x,const P &y){return x.fi > y.fi || (x.fi == y.fi && x.se > y.se);});
scanf("%d",&m);
FOR(i,1,m) scanf("%d",w+i);std::sort(w+1,w+m+1);std::reverse(w+1,w+m+1);
int ans = 0;
FOR(i,1,n){
lim[i] = lim[i-1];
while(lim[i]+1 <= m && w[lim[i]+1] >= a[i].fi) ++lim[i];
int f = bit.query(a[i].se)+1;f = std::min(f,lim[i]);
bit.add(a[i].se,f);
ans = std::max(ans,f);
}
printf("%d
",ans);
}
int main(){
// freopen("C.in","r",stdin);
int T;scanf("%d",&T);
while(T--) Solve();
return 0;
}
D. 蚂蚁
先考虑不需要支持修改怎么做:假设现在有 (k) 只蚂蚁,所在的点分别为 (v_1ldots v_k),设每个蚂蚁到达根的时间为 (t_i),答案就是 (max t_i),我们观察一下有哪些限制:
首先,每个蚂蚁的 (t_i geq dep_{v_i}),因为就算从开始爬也要花这些时间才能过去。
然后,我们要保证不会在某时刻在同一个点,我们可以发现如果两只蚂蚁在某个时候在同一个点上,那么它们在根的时候也会在同一个点上,所以我们只需要保证 (t_i) 互不相同就行了。
所以问题变成了,每个变量有一个下界 (lim_i),要求分配使得变量互不相同,并且最大值尽量小,这是个经典问题:考虑记 (s_i = sum_{x=1}^k [lim_x geq i]),答案显然是 (max(s_i+i-1)),用线段树维护即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 1e5 + 5;
int mx[MAXN<<2],tag[MAXN<<2];
#define lc ((x)<<1)
#define rc ((x)<<1|1)
inline void cover(int x,int d){
mx[x] += d;tag[x] += d;
}
inline void pushdown(int x){
if(tag[x]){
cover(lc,tag[x]);
cover(rc,tag[x]);
tag[x] = 0;
}
}
inline void modify(int x,int l,int r,int L,int R,int d){
if(l == L && r == R){
cover(x,d);
return;
}
int mid = (l + r) >> 1;pushdown(x);
if(R <= mid) modify(lc,l,mid,L,R,d);
else if(L > mid) modify(rc,mid+1,r,L,R,d);
else modify(lc,l,mid,L,mid,d),modify(rc,mid+1,r,mid+1,R,d);
mx[x] = std::max(mx[lc],mx[rc]);
}
inline void build(int x,int l,int r){
tag[x] = 0;
if(l == r){
mx[x] = l;return;
}
int mid = (l + r) >> 1;
build(lc,l,mid);build(rc,mid+1,r);
mx[x] = std::max(mx[lc],mx[rc]);
}
int n,m;
std::vector<int> G[MAXN];
int dep[MAXN];
inline void dfs(int v,int fa=0){
dep[v] = dep[fa]+1;
for(auto x:G[v]) if(x != fa) dfs(x,v);
}
inline int query(int x,int l,int r,int L,int R){
if(L > R) return 1;
if(l == L && r == R) return mx[x];
int mid = (l + r) >> 1;pushdown(x);
if(R <= mid) return query(lc,l,mid,L,R);
if(L > mid) return query(rc,mid+1,r,L,R);
return std::max(query(lc,l,mid,L,mid),query(rc,mid+1,r,mid+1,R));
}
std::multiset<int> S;
int main(){
scanf("%d%d",&n,&m);
FOR(i,2,n){
int f;scanf("%d",&f);
G[f].pb(i);
}
dfs(1);build(1,1,n);
S.insert(0);
FOR(i,1,m){
int opt,x;scanf("%d%d",&opt,&x);
if(opt == 1){
if(x != 1){
S.insert(dep[x]);
modify(1,1,n,1,dep[x],1);
}
}
else{
if(x != 1){
S.erase(S.find(dep[x]));
modify(1,1,n,1,dep[x],-1);
}
}
int t = *S.rbegin();
printf("%d
",query(1,1,n,1,t)-1);
}
return 0;
}
E. 划分
为啥完全没有思路。。。
我们考虑固定 (1) 为根,定义每个连通块的根为连通块深度最小的点,我们枚举其中一个连通块的根为 (x eq 1),那么有一个连通块根一定为 (1),我们设剩下的那个连通块的根为 (y),分类讨论:
如果 (sz_x = |A|):
- 如果 (y) 不是 (x) 的祖先,(sz_y = sz_x) 或 (sz_y=n-2sz_x)
- 如果 (y) 是 (x) 的祖先,(sz_y=2sz_x) 或 (sz_y=n-sz_x)
如果 (sz_x = |C|):
- 如果 (y) 不是 (x) 的祖先,(sz_y=frac{n-sz_x}{2})
- 如果 (y) 是 (x) 的祖先,(sz_y = frac{n-sz_x}{2}+sz_x = frac{n+sz_x}{2})
相当于我们对于一个点,想要询问到根的链上是否有点的 (sz) 是某个值,这个可以 dfs 的时候处理一个数组搞出来;还需要询问某个点子树外并且不是它的祖先的所有点中是否有点 (sz) 是某个值,预先处理所有的点的 (sz) 出现情况,补集转化后变成了求子树内某个值的出现次数,这个可以用类似差分计算增量的方法实现,也就是在进入子树前记录一下要查询的值,然后进入这个子树,遍历完所有的点并且加到桶里后再求一下值,做个差就能得到子树的值。详情可以看代码
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 1e6 + 5;
std::vector<int> G[MAXN];
int n;
int sz[MAXN];
int c1[MAXN],c2[MAXN],c3[MAXN];
// c1: 整棵树
// c2: 查询子树用
// c3: 祖先
inline void dfs1(int v,int fa=0){
sz[v] = 1;
for(auto x:G[v]){
if(x == fa) continue;
dfs1(x,v);
sz[v] += sz[x];
}
++c1[sz[v]];
}
int ans = 0;
inline void dfs2(int v,int fa=0){
if(2*sz[v] <= n && c3[2*sz[v]] && 2*sz[v] < n) ans = std::max(ans,sz[v]);
if(c3[n-sz[v]] && 2*sz[v] < n) ans = std::max(ans,sz[v]);
if(!((n+sz[v])&1) && c3[(n+sz[v])>>1]) ans = std::max(ans,(n-sz[v])>>1);
int p1 = c2[sz[v]],p2 = n-2*sz[v] > 0 ? c2[n-2*sz[v]] : -1e9,p3 = (n-sz[v])&1 ? -1e9 : c2[(n-sz[v])>>1];
++c3[sz[v]];++c2[sz[v]];
for(auto x:G[v]){
if(x == fa) continue;
dfs2(x,v);
}
p1 = c1[sz[v]]-(c2[sz[v]]-p1);
if(p2 != -1e9) p2 = c1[n-2*sz[v]]-(c2[n-2*sz[v]]-p2);
if(p3 != -1e9) p3 = c1[(n-sz[v])>>1]-(c2[(n-sz[v])>>1]-p3);
if(p1 > 0 && 2*sz[v] < n) ans = std::max(ans,sz[v]);
if(p2 > 0 && 2*sz[v] < n) ans = std::max(ans,sz[v]);
if(p3 > 0) ans = std::max(ans,(n-sz[v])>>1);
--c3[sz[v]];
}
int main(){
scanf("%d",&n);
FOR(i,2,n){
int u,v;scanf("%d%d",&u,&v);
G[u].pb(v);G[v].pb(u);
}
dfs1(1);dfs2(1);
printf("%d
",ans);
return 0;
}