友谊赛题解

2024-03-12 18:48
文章标签 题解 友谊赛

本文主要是介绍友谊赛题解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

A-Rainyrabbit 爱邮递

算法 1

每次重建快递站后直接深搜一遍累加贡献就好了,然后 O ( 1 ) \mathcal{O}(1) O(1) 回答。

你可以获得 10pts 的好成绩。

算法 2

菊花图,这个很简单啊。

如果加的是花心,相当于其他城市都加 1 1 1,否则就是花心的城市加 1 1 1,其他城市加 2 2 2

结合算法 1 可以获得 30pts 的好成绩。

算法 3

一条链的话也是送的,直接拆拆贡献用棵线段树维护即可。

结合算法 1,2 可以获得 60pts 的好成绩。

算法 4

做法 1

考虑树怎么做,很套路的可以将 d i s ( x , y ) dis(x,y) dis(x,y) 拆成 d e p x + d e p y − 2 d e p lca ⁡ ( x , y ) dep_x+dep_y-2dep_{\operatorname{lca}(x,y)} depx+depy2deplca(x,y),但是拆了还是不好做,修改最坏还是 O ( n ) \mathcal{O(n)} O(n) 的。

发现 d e p lca ⁡ ( x , y ) dep_{\operatorname{lca}(x,y)} deplca(x,y) 这个玩意可以借助 [LNOI2014]LCA 的思想,将树根 1 1 1 x x x 上的边都加上一次这条边的权值,然后节点 y y y 往上爬,途中边排边累加该边的贡献,爬到根后就是 d e p lca ⁡ ( x , y ) dep_{\operatorname{lca}(x,y)} deplca(x,y) 的值。

于是便有了一个新的算法,对于重建快递站到 u u u,直接将 u u u 往上爬,并更新每条边新的贡献,查询的话就跟刚才一样的往上爬就好了。

暴力往上爬的话可以过数据随机的点,然后结合算法 1,2,3 就能获得 80pts 的好成绩。

正解直接用树链剖分优化即可。

时间复杂度 O ( n log ⁡ 2 n ) \mathcal{O}(n\log^2 n) O(nlog2n)

#include <cstdio>
#include <cstring>
#include <queue>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
#define int long long
int n, m, totdep, tot, a[200005], Dep[200005], fa[200005], size[200005], dep[200005], son[200005],seg[200005], rev[200005], top[200005];
struct node {int to, w;
};
vector<node> G[200005];
void dfs1(int u, int f) {size[u] = 1, dep[u] = dep[f] + 1, fa[u] = f;for (int i = 0; i < G[u].size(); i++) {int v = G[u][i].to;if (v == f)continue;Dep[v] = Dep[u] + G[u][i].w;a[v] = G[u][i].w;dfs1(v, u);size[u] += size[v];if (size[v] > size[son[u]])son[u] = v;}
}
void dfs2(int u, int f) {if (son[u]) {seg[son[u]] = ++seg[0];top[son[u]] = top[u];rev[seg[0]] = son[u];dfs2(son[u], u);}for (int i = 0; i < G[u].size(); i++) {int v = G[u][i].to;if (v == f)continue;if (!top[v]) {seg[v] = ++seg[0];top[v] = v;rev[seg[0]] = v;dfs2(v, u);}}
}
struct Segment_Tree {struct node {LL xsum, sum, lazy;};node tree[800005];
#define lson(x) x << 1
#define rson(x) x << 1 | 1void build(int x, int l, int r) {if (l == r) {tree[x].sum = a[rev[l]];return;}int mid = (l + r) >> 1;build(lson(x), l, mid);build(rson(x), mid + 1, r);tree[x].sum = tree[lson(x)].sum + tree[rson(x)].sum;tree[x].xsum = tree[lson(x)].xsum + tree[rson(x)].xsum;}void push_down(int x) {if (tree[x].lazy) {tree[lson(x)].lazy += tree[x].lazy;tree[rson(x)].lazy += tree[x].lazy;tree[lson(x)].xsum += tree[x].lazy * tree[lson(x)].sum;tree[rson(x)].xsum += tree[x].lazy * tree[rson(x)].sum;tree[x].lazy = 0;}}void add(int x, int l, int r, int L, int R) {if (L <= l && r <= R) {tree[x].lazy++;tree[x].xsum += tree[x].sum;return;}push_down(x);int mid = (l + r) >> 1;if (L <= mid)add(lson(x), l, mid, L, R);if (mid + 1 <= R)add(rson(x), mid + 1, r, L, R);tree[x].xsum = tree[lson(x)].xsum + tree[rson(x)].xsum;}LL query(int x, int l, int r, int L, int R) {if (L <= l && r <= R)return tree[x].xsum;LL ans = 0;int mid = (l + r) >> 1;push_down(x);if (L <= mid)ans += query(lson(x), l, mid, L, R);if (mid + 1 <= R)ans += query(rson(x), mid + 1, r, L, R);return ans;}
} T;
void change(int u) {while (u) {int fu = top[u];T.add(1, 1, n, seg[fu], seg[u]);u = fa[fu];}
}
LL query(int u) {LL ans = 0;while (u) {int fu = top[u];ans += T.query(1, 1, n, seg[fu], seg[u]);u = fa[fu];}return ans;
}
signed main() {scanf("%lld %lld", &n, &m);for (int i = 1; i < n; i++) {int u, v, w;scanf("%lld %lld %lld", &u, &v, &w);G[u].push_back(node{ v, w });G[v].push_back(node{ u, w });}dfs1(1, 0);seg[0] = 1, seg[1] = 1, top[1] = rev[1] = 1;dfs2(1, 0);T.build(1, 1, seg[0]);while (m--) {int opt, u;scanf("%lld %lld", &opt, &u);if (opt == 1)change(u), totdep += Dep[u], tot++;elseprintf("%lld\n", totdep + 1ll * tot * Dep[u] - 2 * query(u));}return 0;
}

做法 2

来自巨佬 WY 的做法,cdq 分治+虚树。

B-Rainyrabbit 爱回文

很遗憾,因为数据过水,导致有 5 人 AC,但是本质上只有 3 人写的正解,其他的时间复杂度都是错的。

多次询问一个字符串 s s s,问构造若干字符串 t 1 , t 2 , ⋯ , t k t_1,t_2,\cdots ,t_k t1,t2,,tk 使得 s = t 1 t 2 ⋯ t k s=t_1t_2\cdots t_k s=t1t2tk 并且 ∀ i ∈ [ 1 , k ] , t i ≥ 2 \forall i \in [1,k],t_i \geq 2 i[1,k],ti2 并且是回文串的方式是否存在。 T ≤ 10 , ∣ s ∣ ≤ 1 0 6 T \leq 10,|s| \leq 10^6 T10,s106

首先每次选最大的回文串肯定是错的。具体看这个串 ababallllllab

先求出 f f f f i f_i fi 为以字符 i i i 为开头的最短回文串长度。

考虑这个辅助数组的作用。假设我们的合法划分方案,在这里的划分出来的回文串长度是 d d d,那么这个 f i f_i fi d d d 的关系是什么呢?

首先,显然 f i ≤ d f_i \leq d fid。分类讨论:

  • f i = d f_i = d fi=d:那没事儿了。
  • f i < d 2 f_i < \dfrac{d}{2} fi<2d:显然我们可以将这个字符串 d d d 看成三个回文串,也就是一段 f i f_i fi,以最后一个字符为中心对称过去,或者是再恰一个字符再对称,或者是直接对称过去;
  • 否则这种情况是不可能的,因为这样我们能够找到更小的前缀回文串。可以自行理解。注意这个更小的前缀回文串可能是一个字母,需要特判。

综上,以 i i i 开头的回文串长度的选择只有四个: f i , 2 f i − 1 , 2 f i + 1 , 2 f i f_i,2f_i-1,2f_i+1,2f_i fi,2fi1,2fi+1,2fi。可以证明其可以涵盖所有情况。剩下的工作就是做个 dp。

考虑求出这个 f f f 数组。有三种做法,时间复杂度分别是 O ( n log ⁡ n ) , O ( n α ( n ) ) O(n \log n),O(n \alpha(n)) O(nlogn),O(nα(n))。标程给的是 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的做法。 O ( n α ( n ) ) O(n \alpha(n)) O(nα(n)) 做法由 @chihik 提出。

理论上来说有 O ( n ) O(n) O(n) 的做法,但是我太菜没听懂 @crashed 再说啥。

O ( n log ⁡ n ) O(n \log n) O(nlogn)

考虑对于每一个字符,求出以它为中心(中心可以是和下一个字符结合也可以是一个字符,这个是两类)的最长回文串,在其左端点打上标记。标记记录三个值,表示其起始位置,有效长度及中心是和下一个字符结合还是一个字符。做法是 hash 二分。你高兴也可以 manacher。

这个东西的意义是,我们选择的 f i f_i fi,一定是离有效范围内结束最近的那个位置的距离。于是处理三个东西,可以发现这个东西可以全部拍进 set 里面选最小的且合法的。具体处理方法看代码。

代码有个小补丁,就是要处理两个相邻字符相同的情况。这个时候直接把 f i f_i fi 设为 2 2 2 即可。

/*
LL         JJ  SSSS    ii  ssss     SSSS BBBB
LL         JJ S           s        S     B   B
LL         JJ  SSS     ii  sss      SSS  BBBB
LL     JJ  JJ     S    ii     s        S B   B
LLLLLL  JJJJ  SSSS     ii ssss     SSSS  BBBBSSSS BBBB     ii  ssss    LL         JJ  SSSS
S     B   B       s        LL         JJ SSSS  BBBB     ii  sss     LL         JJ  SSSS B   B    ii     s    LL     JJ  JJ     S
SSSS  BBBB     ii ssss     LLLLLL  JJJJ  SSSS
*/
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const ULL base=10086001;
ULL lhsh[3000005],rhsh[3000005],pw[3000005];
char s[3000005];
ULL getHashL(int l,int r){return lhsh[r]-lhsh[l-1]*pw[r-l+1];}
ULL getHashR(int l,int r){return rhsh[l]-rhsh[r+1]*pw[r-l+1];}
int n,f[3000005];
bool isPalindrome(int l,int r){return getHashL(l,r)==getHashR(l,r);}
struct pldrome{int pos,len,type;pldrome(int R=0,int L=0,int T=0){pos=R,len=L,type=T;}bool operator != (const pldrome ano) const {return pos!=ano.pos || len!=ano.len || type!=ano.type;}bool operator < (const pldrome ano) const {int ps=pos,ln=len,tp=type,pst=ano.pos,lnt=ano.len,tpt=ano.type;if(tp==0)	--ln;if(tpt==0)	--lnt;int ed1=ps+ln-1,ed2=pst+lnt-1;return ed1<ed2;}
};
vector<pldrome> G[3000005];
bool dp[3000005];
int main(){ pw[0]=1;for(int i=1;i<=3000000;++i)	pw[i]=pw[i-1]*base; int T;scanf("%d",&T);dp[0]=true;while(T-->0){scanf("%s",s+1);n=strlen(s+1);for(int i=1;i<=n;++i)	lhsh[i]=lhsh[i-1]*base+ULL(s[i]),f[i]=0;for(int i=n;i>=1;--i)	rhsh[i]=rhsh[i+1]*base+ULL(s[i]),dp[i]=false;for(int i=1;i<=n;++i){if(i==1){if(isPalindrome(1,2))	G[1].push_back(pldrome(1,1,1));}else if(i==n-1){if(isPalindrome(n-1,n))	G[n-1].push_back(pldrome(n-1,1,1));if(isPalindrome(n-2,n))	G[n-2].push_back(pldrome(n-2,2,0));}else if(i==n)	continue;else{//i is mediumint l=0,r=min(i-1,n-i),ans=0;while(l<=r){int mid=(l+r)>>1;if(isPalindrome(i-mid,i+mid))	ans=mid,l=mid+1;else	r=mid-1;}if(ans)	G[i-ans].push_back(pldrome(i-ans,ans+1,0));l=0,r=min(i-1,n-i-1),ans=0;while(l<=r){int mid=(l+r)>>1;if(isPalindrome(i-mid,i+1+mid))	ans=mid,l=mid+1;else	r=mid-1;}if(ans)	G[i-ans].push_back(pldrome(i-ans,ans+1,1));else{if(isPalindrome(i,i+1))	G[i].push_back(pldrome(i,1,1));}}}set<pldrome> S;for(int i=1;i<=n;++i){while(!G[i].empty())	S.insert(G[i].back()),G[i].pop_back();while(!S.empty()){pldrome st=*S.begin();int ps=st.pos,ln=st.len,tp=st.type;if(tp==0)	--ln;int ed=ps+ln-1;if(ed<i){S.erase(S.begin());continue;}if(tp==0){++ed;int edTrue=2*ed-i;f[i]=edTrue-i+1;}else{int edTrue=2*ed-i+1;f[i]=edTrue-i+1;}break;}if(S.empty())	f[i]=-1;if(i<=n-1 && isPalindrome(i,i+1))	f[i]=2;	}for(int i=1;i<=n;++i){if(f[i]==-1 || !dp[i-1])	continue;dp[i+f[i]-1]=true;int sp=2*f[i]-1;if(isPalindrome(i,i+sp-1))	dp[i+sp-1]=true;sp+=2;if(isPalindrome(i,i+sp-1))	dp[i+sp-1]=true;--sp;if(isPalindrome(i,i+sp-1))	dp[i+sp-1]=true;}puts(dp[n]?"YeseY":"NoN");}return 0;
}

O ( n α ( n ) ) O(n \alpha (n)) O(nα(n)) By @chihik

考虑回文自动机。一样的思路,求出以某位为起点的最长回文串。暴力回跳。显然 T,于是并查集优化即可。

O ( n ) O(n) O(n) By crashed / LY(初二)

本意是选联赛范围考察。但是因为大家都会回文自动机所以被踩了。反正其实我也不会。

具体做法是链上排一次序(基排 By crashed)。

C-Rainyrabbit 爱求和

菜鸡出的垃圾套路题。/kk

算法 1

测试点 1 1 1 随便手玩。

期望得分 4 分。

算法 2

测试点 2 ∼ 3 2\sim 3 23 按照题意模拟。

期望得分 12 分。

算法 3

测试点 4 ∼ 5 4\sim 5 45 先在外面直接算然后直接回答。

期望得分 20 分。


#include<cstdio>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
const int mod=998244353;
int dec(int x, int y) { return x >= y ? x - y : x + mod - y; }
int mul(int x, int y) { return 1ll * x * y % mod; }
int add(int x, int y) {if (x + y >= mod)return x + y - mod;return x + y;
}
int qkpow(LL a, LL b) {int res = 1;for (; b; b >>= 1, a = mul(a, a))if (b & 1)res = mul(res, a);return res;
}
int t,mem[105][105][105];
LL a,b,c;
LL lcm(LL a,LL b){return a/__gcd(a,b)*b;
}
LL sigma(LL n,LL k){LL ans=0;for(int i=1;i<=n;i++){if(n%i==0)ans=add(ans,qkpow(i,k));}return ans;
}
LL f(LL n,LL m,LL k){LL ans=0;for(LL i=m;i<=n;i+=m)ans=add(ans,sigma(i,k));return ans;
}
signed main(){cin>>t>>c;for(int i=1;i<=100;i++)for(int j=1;j<=100;j++)for(int k=0;k<=c;k++)mem[i][j][k]=k==0?f(i,j,0):add(mem[i][j][k-1],f(i,j,k));for(int i=1;i<=100;i++){for(int j=1;j<=100;j++){mem[i][j][c]=add(mem[i][j][c],add(mem[i][j-1][c],mem[i-1][j][c]));mem[i][j][c]=dec(mem[i][j][c],mem[i-1][j-1][c]);}}while(t--){cin>>a>>b;cout<<mem[a][b][c]<<endl;}return 0;
} 
/*
5 5
4 1 3 6 2
1 2 4
2 1 3
0 0 3 2
1 1 2
2 2 4
*/

算法 4

送分结束。

来推一推 f f f 函数。

f ( n , m , k ) = ∑ d = 1 n d k ⌊ n lcm ⁡ ( d , m ) ⌋ = ∑ d = 1 n d k ⌊ n d ⌊ m gcd ⁡ ( d , m ) ⌋ ⌋ f(n,m,k)=\sum_{d=1}^n d^k \lfloor\dfrac{n}{\operatorname{lcm}(d,m)}\rfloor=\sum_{d=1}^n d^k \lfloor\dfrac{\dfrac{n}{d}}{\lfloor\dfrac{m}{\gcd(d,m)}\rfloor}\rfloor f(n,m,k)=d=1ndklcm(d,m)n=d=1ndkgcd(d,m)mdn

这个东西是有意义的, ∑ d = 1 n d k ⌊ n d ⌊ m gcd ⁡ ( d , m ) ⌋ ⌋ = ∑ i = 1 ⌊ n d ⌋ [ i d ∣ m ] \sum_{d=1}^n d^k \lfloor\dfrac{\dfrac{n}{d}}{\lfloor\dfrac{m}{\gcd(d,m)}\rfloor}\rfloor=\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}[id|m] d=1ndkgcd(d,m)mdn=i=1dn[idm]

然后拆回来

f ( n , m , k ) = ∑ m ∣ i n ∑ d ∣ i d k = ∑ m ∣ i n σ k ( i ) f(n,m,k)=\sum_{m|i}^n \sum_{d|i}d^k=\sum_{m|i}^n \sigma_k(i) f(n,m,k)=mindidk=minσk(i)

结论就推完了。

代回去

∑ k = 0 c ∑ i = 1 a ∑ j = 1 b ∑ j ∣ p i σ k ( p ) \sum_{k=0}^c\sum_{i=1}^a\sum_{j=1}^b\sum_{j|p}^i\sigma_k(p) k=0ci=1aj=1bjpiσk(p)

∑ k = 0 c ∑ p = 1 a σ k ( p ) ( a − p + 1 ) ∑ j = 1 b [ j ∣ p ] \sum_{k=0}^c\sum_{p=1}^a\sigma_k(p)(a-p+1)\sum_{j=1}^b[j|p] k=0cp=1aσk(p)(ap+1)j=1b[jp]

σ k ( p ) \sigma_k(p) σk(p) 那里可以直接把 σ 0 ( p ) , σ 1 ( p ) . . . , σ c ( p ) \sigma_0(p),\sigma_1(p)...,\sigma_c(p) σ0(p),σ1(p)...,σc(p) 都累加到一坨,然后直接 O ( n 2 ) \mathcal{O}(n^2) O(n2) 计算即可。

跟刚才一样的操作,先在外面都算出来再回答,可以获得 36pts 的好成绩。

算法 5

发现 b b b 的那个求和并不需要一个一个算,直接调和级数复杂度算就好了,然后就能获得 40 pts。

算法 6

c c c 太大了,推一推怎么算 ∑ i = 0 c σ i ( p ) \sum_{i=0}^c \sigma_i(p) i=0cσi(p)

∑ i = 0 c ∑ d ∣ p d i \sum_{i=0}^c \sum_{d|p}d^i i=0cdpdi

发现对于每一个因子的贡献都是一个等比数列。

∑ d ∣ p d c + 1 − 1 d − 1 \sum_{d|p}\dfrac{d^{c+1}-1}{d-1} dpd1dc+11

线性筛后调和级数算就好了,然后就能获得 68 pts 了。

算法 7

发现 b b b 超过 a a a 的范围以后是不会有任何贡献的,于是取个 min ⁡ \min min 算,至此就能获得 80 pts 的好成绩。

算法 8

这东西并不好多组询问, a , b a,b a,b 变来变去的很烦。

考虑离线,将每一个询问按照 b b b 排序。当 b = i b=i b=i 时能影响到的位置是 n i \dfrac{n}{i} in 个,直接树状数组修改即可。修改完后,直接查询就好了。

时间复杂度 O ( a ln ⁡ a + T log ⁡ T + min ⁡ ( a , b ) ln ⁡ min ⁡ ( a , b ) log ⁡ a + T log ⁡ a ) \mathcal{O}(a\ln a+T\log T+\min(a,b)\ln \min(a,b)\log a+T\log a) O(alna+TlogT+min(a,b)lnmin(a,b)loga+Tloga)

由于这东西并跑不满,跑 1 0 6 10^6 106 是跑的过的。代码及其好写。

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
const int mod = 998244353;
int dec(int x, int y) { return x >= y ? x - y : x + mod - y; }
int mul(int x, int y) { return 1ll * x * y % mod; }
int add(int x, int y) {if (x + y >= mod)return x + y - mod;return x + y;
}
int qkpow(LL a, LL b) {int res = 1;for (; b; b >>= 1, a = mul(a, a))if (b & 1)res = mul(res, a);return res;
}
int T, cnt, maxa, a[200005], prime[1000005], pw[1000005], inv[1000005], f[1000005], s[1000005], anos[1000005],ans[1000005];
bool vis[1000005];
LL b[200005], c;
struct node {int id, a;
};
vector<node> G[1000005];
struct BIT {int tree[1000005];void Add(int x, int val) {while (x <= maxa) {tree[x] = add(tree[x], val);x += x & (-x);}}int query(int x) {int Ans = 0;while (x) {Ans = add(Ans, tree[x]);x -= x & (-x);}return Ans;}
} T1, T2;
void seive() {pw[1] = 1;for (int i = 2; i <= 1000000; i++) {if (!vis[i])prime[++cnt] = i, pw[i] = qkpow(i, c + 1);for (int j = 1; j <= cnt && i * prime[j] <= 1000000; j++) {vis[i * prime[j]] = 1;pw[i * prime[j]] = mul(pw[i], pw[prime[j]]);if (i % prime[j] == 0)break;}}inv[1] = 1;for (int i = 2; i <= 1000000; i++) inv[i] = (LL)(mod - mod / i) * inv[mod % i] % mod;f[1] = add(c % mod, 1);for (int i = 2; i <= 1000000; i++) f[i] = mul(dec(pw[i], 1), inv[i - 1]);for (int i = 1; i <= 1000000; i++)for (int j = i; j <= 1000000; j += i) s[j] = add(s[j], f[i]);for (int i = 1; i <= 1000000; i++) anos[i] = mul(s[i], i);
}
signed main() {scanf("%d %lld", &T, &c);seive();for (int i = 1; i <= T; i++)scanf("%d %lld", &a[i], &b[i]), maxa = max(maxa, a[i]), b[i] = min(b[i], 1ll * a[i]);for (int i = 1; i <= T; i++) G[b[i]].push_back(node{ i, a[i] });for (int i = 1; i <= maxa; i++) {for (int j = i; j <= maxa; j += i) T1.Add(j, s[j]), T2.Add(j, anos[j]);for (int j = 0; j < G[i].size(); j++) {node now = G[i][j];ans[now.id] = dec(mul(now.a + 1, T1.query(now.a)), T2.query(now.a));}}for (int i = 1; i <= T; i++) printf("%d\n", ans[i]);return 0;
}

D-Rainyrabbit 爱染色

本场比赛的防 AK 题。其实思维难度不算太大,只是极其难写。

算法 1

爆搜即可。可以获得 10 pts。

算法 2

考虑 O ( n m ) \mathcal{O}(nm) O(nm) 的做法。

考虑贪心,每次选择只由该关键点覆盖的节点个数最多的关键点拿来染色,选完后重新更新一下(因为选了一个关键点染色),重复以上步骤 m m m 次,期间更新一下答案就好了,容易证明得出的答案一定是最优的。

期望得分 40 pts。

算法 3

先来考虑优化链的情况。

发现每个关键点控制的范围都是一个连续的区间,用一棵线段树维护即可。

菊花图就更简单了,如果花心 d i ≥ 1 d_i\ge 1 di1 全部都能控制到,其他的点如果 d i = 1 d_i=1 di=1 就只能控制花心, d i = 2 d_i=2 di=2 就能控制所有的点。随便更新一下就好了。

结合算法 2 期望得分 60pts。

算法 4

发现复杂度瓶颈在于怎么快速找到只由该关键点覆盖的节点个数最多的关键点及其更新。

先来看看 d i ∈ 0 , 1 d_i\in{0,1} di0,1 的情况,因为每个关键点能覆盖的节点是散开的,不好处理。于是考虑将树从上到下每一层从左到右编个号,然后你会惊奇的发现对于 d i = 1 d_i=1 di=1 的关键点能染到的点可以看成一个连续的区间和该关键点父亲的编号。于是可以想到用线段树维护,用每一个点的编号建树,但是发现不太好维护,因为要知道是哪一个关键点,而不是最多的点数,于是想到在线段树的每一个节点上都开一个 set 维护这个区间编号的节点有哪些关键点能够染到,找到后删除的话就直接 erase 就好了。现在的问题,怎么找????考虑直接从根节点往下搜,直接暴力找肯定会 T,考虑对于线段树上的每一个节点都维护一个区间最小值,表示这个区间至少有多少个关键点覆盖,如果关键点个数大于 1 1 1 或者已经被选过的关键点染过就不搜了,否则就拿出 set 里的关键点,往下搜,搜到 l = r l=r l=r 时就将该关键点的 c n t cnt cnt 加一,放进堆里就好了。

关于找最小字典序的序列也很简单,每次选个数最多编号最小的关键点就好了,然后跟上面一样跑。

期望得分 70pts。

算法 5

现在来玩 d i = 2 d_i=2 di=2 的情况,发现也可以拆分成几个编号连续的区间。该关键点的下面两层,它的父亲的父亲,它的父亲的父亲下面那一层。然后跟上面那样一样做就行。

时间复杂度 O ( m log ⁡ 2 n ∼ m log ⁡ 3 n ) \mathcal{O}(m\log^2 n\sim m\log^3n ) O(mlog2nmlog3n)

#include <set>
#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n, m, Cnt, ans = 1e9;
int im_node[3][200005], cnt[200005], fa[200005], L1[200005], R1[200005], L2[200005], R2[200005], dep[200005], dfn[200005];
bool isdead[200005];#define pi pair<int, int>
#define lson(x) x << 1
#define rson(x) x << 1 | 1struct node {int minn, lazy;
} tree[400005];set<int>::iterator it;
set<int> cover[400005];vector<int> G[200005], g[200005];priority_queue<pi> Q1;
priority_queue<int> Q2;void dfs1(int u, int f) {dep[u] = dep[f] + 1, fa[u] = f;g[dep[u]].push_back(u);for (int i = 0; i < G[u].size(); i++) {int v = G[u][i];if (v == f)continue;dfs1(v, u);}
}void dfs2(int u, int f) {L1[u] = L2[u] = 1e9, R1[u] = R2[u] = 0;for (int i = 0; i < G[u].size(); i++) {int v = G[u][i];if (v == f)continue;dfs2(v, u);L1[u] = min(L1[u], dfn[v]);L2[u] = min(L2[u], L1[v]);R1[u] = max(R1[u], dfn[v]);R2[u] = max(R2[u], R1[v]);}
}void rebuild(int x, int l, int r) {tree[x].lazy = tree[x].minn = 0;if (l == r)return;int mid = (l + r) >> 1;rebuild(lson(x), l, mid);rebuild(rson(x), mid + 1, r);
}void push_down(int x) {if (tree[x].lazy) {tree[lson(x)].lazy += tree[x].lazy;tree[rson(x)].lazy += tree[x].lazy;tree[lson(x)].minn += tree[x].lazy;tree[rson(x)].minn += tree[x].lazy;tree[x].lazy = 0;}
}void Updata(int x, int l, int r, int u) {if (tree[x].minn > 1)return;if (cover[x].size())u = *cover[x].begin();if (l == r) {if (tree[x].minn == 0)tree[x].minn = 1e9;else if (tree[x].minn == 1) {cnt[u]++;Q1.push(make_pair(cnt[u], u));tree[x].minn = 1e9;}return;}push_down(x);int mid = (l + r) >> 1;Updata(lson(x), l, mid, u);Updata(rson(x), mid + 1, r, u);tree[x].minn = min(tree[lson(x)].minn, tree[rson(x)].minn);
}void sub(int x, int l, int r, int L, int R, int u) {if (L <= l && r <= R) {tree[x].lazy--, tree[x].minn--;it = cover[x].find(u);cover[x].erase(it);return;}push_down(x);int mid = (l + r) >> 1;if (L <= mid)sub(lson(x), l, mid, L, R, u);if (mid + 1 <= R)sub(rson(x), mid + 1, r, L, R, u);tree[x].minn = min(tree[lson(x)].minn, tree[rson(x)].minn) + tree[x].lazy;
}void add(int x, int l, int r, int L, int R, int u) {if (L <= l && r <= R) {tree[x].lazy++, tree[x].minn++;cover[x].insert(u);return;}push_down(x);int mid = (l + r) >> 1;if (L <= mid)add(lson(x), l, mid, L, R, u);if (mid + 1 <= R)add(rson(x), mid + 1, r, L, R, u);tree[x].minn = min(tree[lson(x)].minn, tree[rson(x)].minn);
}void updata(int opt, int u, int l, int r) {if (l > r)return;if (opt == 1)add(1, 1, n, l, r, u);elsesub(1, 1, n, l, r, u);
}void add_or_sub_im_node(int x, int opt) {int u = im_node[0][x], d = im_node[1][x];if (d == 0)updata(opt, x, dfn[u], dfn[u]);else if (d == 1) {updata(opt, x, L1[u], R1[u]);updata(opt, x, dfn[u], dfn[u]);if (fa[u])updata(opt, x, dfn[fa[u]], dfn[fa[u]]);} else {updata(opt, x, L1[u], R1[u]);updata(opt, x, L2[u], R2[u]);if (fa[u]) {int v = fa[u];updata(opt, x, L1[v], R1[v]);updata(opt, x, dfn[v], dfn[v]);if (fa[v])updata(opt, x, dfn[fa[v]], dfn[fa[v]]);} elseupdata(opt, x, dfn[u], dfn[u]);}
}int main() {scanf("%d %d", &n, &m);for (int i = 1; i < n; i++) {int u, v;scanf("%d %d", &u, &v);G[u].push_back(v);G[v].push_back(u);}dfs1(1, 0);for (int i = 1; i <= n; i++)for (int j = 0; j < g[i].size(); j++) dfn[g[i][j]] = ++Cnt;dfs2(1, 0);for (int i = 1; i <= m; i++) {scanf("%d %d", &im_node[0][i], &im_node[1][i]);add_or_sub_im_node(i, 1);}for (int i = 1; i <= m; i++) Q1.push(make_pair(0, i));Updata(1, 1, n, -1);for (int i = 1; i <= m; i++) {while (isdead[Q1.top().second] || Q1.top().first != cnt[Q1.top().second]) Q1.pop();pi now = Q1.top();Q1.pop();ans = min(ans, now.first);isdead[now.second] = 1;add_or_sub_im_node(now.second, -1);Updata(1, 1, n, -1);}printf("%d\n", ans);rebuild(1, 1, n);while (!Q1.empty()) Q1.pop();for (int i = 1; i <= m; i++) isdead[i] = 0, cnt[i] = 0, add_or_sub_im_node(i, 1), Q1.push(make_pair(0, i));Updata(1, 1, n, -1);int choose = 0;for (int i = 1; i <= m; i++) {while (choose < m) {while (isdead[Q1.top().second] || Q1.top().first != cnt[Q1.top().second]) Q1.pop();if (Q1.top().first < ans)break;Q2.push(-Q1.top().second);isdead[Q1.top().second] = 1;Q1.pop();choose++;}add_or_sub_im_node(-Q2.top(), -1);Q2.pop();Updata(1, 1, n, -1);}for (int i = 1; i <= m; i++) printf("%d\n", cnt[i]);return 0;
}

这篇关于友谊赛题解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/802238

相关文章

C++ | Leetcode C++题解之第393题UTF-8编码验证

题目: 题解: class Solution {public:static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num &

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

C - Word Ladder题解

C - Word Ladder 题解 解题思路: 先输入两个字符串S 和t 然后在S和T中寻找有多少个字符不同的个数(也就是需要变换多少次) 开始替换时: tips: 字符串下标以0开始 我们定义两个变量a和b,用于记录当前遍历到的字符 首先是判断:如果这时a已经==b了,那么就跳过,不用管; 如果a大于b的话:那么我们就让s中的第i项替换成b,接着就直接输出S就行了。 这样

【秋招笔试】9.07米哈游秋招改编题-三语言题解

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 大厂实习经历 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收集 100+ 套笔试题,笔试真题 会在第一时间跟新 🍄 题面描述等均已改编,如果和你笔试题看到的题面描述

LeetCode 第414场周赛个人题解

目录 Q1. 将日期转换为二进制表示 原题链接 思路分析 AC代码 Q2. 范围内整数的最大得分 原题链接 思路分析 AC代码 Q3. 到达数组末尾的最大得分 原题链接 思路分析 AC代码 Q4. 吃掉所有兵需要的最多移动次数 原题链接 思路分析 AC代码 Q1. 将日期转换为二进制表示 原题链接 Q1. 将日期转换为二进制表示 思路分析

牛客小白月赛100部分题解

比赛地址:牛客小白月赛100_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ A.ACM中的A题 #include<bits/stdc++.h>using namespace std;#define ll long long#define ull = unsigned long longvoid solve() {ll a,b,c;cin>>a>>b>

P2858 [USACO06FEB] Treats for the Cows G/S 题解

P2858 题意 给一个数组。每天把最左或者最右的东西卖掉,第 i i i个东西,第 d a y day day天卖出的价格是 a [ i ] ∗ d a y a[i]*day a[i]∗day。 记忆化搜索 void dfs(int l,int r,int day,ll sum){if(v[l][r]>=sum)return;v[l][r]=sum;if(l>r)//这就是dp答案{

【C++题解】1272. 郭远摘苹果

欢迎关注本专栏《C++从零基础到信奥赛入门级(CSP-J)》 问题:1272. 郭远摘苹果 类型:二维数组 题目描述: 郭远有一天走到了一片苹果林,里面每颗树上都结有不同数目的苹果,郭远身上只能拿同一棵树上的苹果,他每到一棵果树前都会把自己身上的苹果扔掉并摘下他所在树上的苹果并带走(假设郭远会走过每一棵苹果树),问在郭远摘苹果的整个过程中,他身上携带的最多苹果数与最小苹果数的差是多少?

【最新华为OD机试E卷-支持在线评测】机器人活动区域(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-E/D卷的三语言AC题解 💻 ACM金牌🏅️团队| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线评测,专栏文章质量平均 94 分 最新华为OD机试目录: https://blog.

2023 CCPC(秦皇岛)现场(第二届环球杯.第 2 阶段:秦皇岛)部分题解

所有题目链接:Dashboard - The 2023 CCPC (Qinhuangdao) Onsite (The 2nd Universal Cup. Stage 9: Qinhuangdao) - Codeforces 中文题面: contest-37054-zh.pdf (codeforces.com) G. Path 链接: Problem - G - Codeforces