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]," \n"[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\n",(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\n",ans);
}
int main(){
// freopen("C.in","r",stdin);
int T;scanf("%d",&T);
while(T--) Solve();
return 0;
}
D. 螞蟻
先考慮不需要支持修改怎麼做:假設現在有 \(k\) 只螞蟻,所在的點分別爲 \(v_1\ldots 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\n",query(1,1,n,1,t)-1);
}
return 0;
}
E. 劃分
爲啥完全沒有思路。。。
我們考慮固定 \(1\) 爲根,定義每個連通塊的根爲連通塊深度最小的點,我們枚舉其中一個連通塊的根爲 \(x \neq 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\n",ans);
return 0;
}