2021.10.10
T1:林下风气
Problem:
里口福因有林下风气,带领全国各地高校掀起了一股 AK 风,大家都十分痴迷于 AK。里口福为了打击大家的自信心,出了一道自以为十分困难的题目。
里口福有一棵树,第 i 个节点上有点权 ai,他的问题就是这棵树中有多少个不同的连通块满足连通块的最大值与最小值之差=k,两个连通块不同当且仅当至少存在一个节点在一个连通块中出现而另一个连通块中没有出现。
第一行两个整数 n,k,表示树的大小以及题目中的 k。
第二行 n 个整数,第 i 个整数表示 ai。
接下来 n-1 行,每行两个整数 x,y 表示树边(x,y)。
Solution:
- 算法 1
2N枚举每个点选或不选,以枚举所有的连通块,再暴力判断是否满足题目要求即可。
时间复杂度O(2n),期望得分30。
- 算法 2
输出0/1/n+2均可获得 10 分,配合随机化(或打点)甚至可能获得30分的好成绩,期望得分10 至30不等。
- 算法 3
树是一条链,把链看成一个序列,那么一个连通块必定对应必定序列上连续的一段区间,枚举左端点,在从左到右枚举右端点,同时记录下最大值与最小值,倘若最大值与最小值差=k,那么答案加 1,时间复杂度O(n2),期望得分20,结合算法 1 可以拿到50分的好成绩。
- 算法 4
当全部 a 都为 0 或 1 时,可以发现只有当 k=0/1 时答案不为0。若 k=0,则说明只有当一个连通块中的点全部都相同时才可行,设݂݅݅ fi 表示从 i 以为根的子树中选出包含 i 且与 i 颜色相同的合法连通块的数量,转移比较简单。若k=1,则用所有的可能情况减去 k=0 的情况即可。时间复杂度 O(n),期望得分20,结合算法 1/3 可以拿到70分的好成绩。
- 算法 5
考虑树形dp。
我们将一个可行的连通块的贡献记在该连通块深度最小的点上,这样便可以做到不重不漏。
O(n)枚举合法连通块的最小值,那么便有唯一的最大值与之对应。
设 fi,0/1,0/1 表示从以 i 为根的子树中选出包含 i,最小值是否出现以及最大值是否出现的连通块的数量,枚举后两维的状态暴力转移即可,注意判断不可行的情况。
时间复杂度 O(n2),常数较大,期望得分100。
Code:
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 const int mod=19260817; 5 const int maxn=1100000; 6 int n,k,u,v,tot,a[maxn]; 7 int head[maxn],nxt[maxn],to[maxn]; 8 inline void add(int u,int v){ 9 tot++; 10 to[tot]=v; 11 nxt[tot]=head[u]; 12 head[u]=tot; 13 } 14 inline int dp(int now,int fa,int r,int k){ 15 if(k<0) return 0; 16 int mid=1; 17 for(int i=head[now];i;i=nxt[i]){ 18 int y=to[i]; 19 if(y==fa) continue; 20 if(a[y]>=a[r]&&a[y]<=a[r]+k&&(a[y]!=a[r]||a[y]==a[r]&&y<r)){ 21 mid=mid*(dp(y,now,r,k)+1)%mod; 22 } 23 } 24 return mid; 25 } 26 signed main(){ 27 cin>>n>>k; 28 for(int i=1;i<=n;i++){ 29 cin>>a[i]; 30 } 31 for(int i=1;i<n;i++){ 32 cin>>u>>v; 33 add(u,v); 34 add(v,u); 35 } 36 int ans=0; 37 for(int i=1;i<=n;i++){ 38 ans=(ans+((dp(i,0,i,k)-dp(i,0,i,k-1))%mod+mod)%mod)%mod; 39 } 40 cout<<ans<<endl; 41 return 0; 42 }
T2:盟主的忧虑
Problem:
江湖由 N 个门派(2≤N≤100,000,编号从 1 到 N)组成,这些门派之间有 N-1 条小道将他们连接起来,每条道路都以“尺”为单位去计量,武林盟主发现任何两个门派都能够直接或者间接通过小道连接。
虽然整个江湖是可以互相到达的,但是他担心有心怀不轨之徒破坏这个武林的安定,破坏小道,于是武林盟主又秘密地修建了 M 条密道(1≤M≤100,000),但每条小道距离都不超过 10 亿尺。
果不其然,最近一个名叫“太吾”的组织意欲破坏武林的小道,请你帮盟主想想办法,如果门派 A 到门派 B 的直连小道被破坏,从 A 走到 B 的所有路径中,经过密道的距离最少是多少?
第一行数字N M
接下来N-1行,每行两个整数A,B,表示A-B间有一条直连小道
接下来M行,每行三个数字 A,B,V,表示A-B间有一条代价为 V 的密道
Solution:
- N<=1000,M<=1000
枚举哪条边是去掉的,然后在剩下的图里面跑最短路(dij或spfa)。
时间复杂度O(N(N+M) log N)
- N<=5000,M<=5000
容易发现,去掉一条树边之后,经过的密道至多只有一条,对于一条密道(u,v,w),在u到v的路径上的边的答案至多为w,暴力更新即可。
时间复杂度O(N M)
- 树是一条链
在上一档部分分中,我们知道,相当于对树中路径做M次的路径的覆盖。
在一条链的情况下,每次相当于对一个区间上的所有元素取min,用线段树维护即可。
时间复杂度O(N log N)
- N,M<=100000
将所有密道按照权值从小到大排序。
对于一条密道(u,v,w),如果u到v的路径上的边中存在边之前还没有被覆盖过,那么说明这条边的答案就是w.
可以用并查集维护,维护每个集合深度最小的节点,对于密道(u,v,w),每次u都在它所在集合中找到深度最小的点,这个点与父亲的连边一定就是上述的边,将这条边的答案更新为w,然后将这个节点与其父亲合并,直到u所在集合的深度最小的节点是小于u和v的lca的,对v做同样的过程即可。
时间复杂度O(Ma(N))
Code:
1 #include<bits/stdc++.h> 2 #define int long long 3 #define ls x<<1 4 #define rs x<<1|1 5 using namespace std; 6 const int maxn=100005; 7 int n,m,vis[maxn],pre[maxn],ans[maxn],use[maxn]; 8 int cnt,son[maxn],se[maxn],be[maxn],dep[maxn],top[maxn]; 9 int tot=1,head[maxn],nex[maxn<<1],to[maxn<<1],fa[maxn]; 10 struct node{ 11 int l,r,mid,id; 12 }t[1000005]; 13 inline int read(){ 14 int x=0,f=1; 15 char ch=getchar(); 16 while(ch<'0'||ch>'9'){ 17 if(ch=='-') f=-1; 18 ch=getchar(); 19 } 20 while(ch>='0'&&ch<='9'){ 21 x=(x<<3)+(x<<1)+ch-'0'; 22 ch=getchar(); 23 } 24 return x*f; 25 } 26 inline void add(int x,int y){ 27 to[++tot]=y; 28 nex[tot]=head[x]; 29 head[x]=tot; 30 } 31 inline void dfs1(int x,int f){ 32 se[x]=1; 33 dep[x]=dep[f]+1; 34 fa[x]=f; 35 for(int i=head[x];i;i=nex[i]){ 36 int y=to[i]; 37 if(y==f) continue; 38 se[x]+=1; 39 dfs1(y,x); 40 if(se[y]>se[son[x]]) son[x]=y; 41 pre[y]=i/2; 42 } 43 } 44 inline void dfs(int x,int tv){ 45 cnt+=1; 46 be[x]=cnt; 47 top[x]=tv; 48 if(son[x]) dfs(son[x],tv); 49 for(int i=head[x];i;i=nex[i]){ 50 int y=to[i]; 51 if(y==fa[x]||y==son[x]) continue; 52 dfs(y,y); 53 } 54 } 55 inline void push_down(int x){ 56 if(t[x].id==1){ 57 if(t[ls].id==0) t[ls].mid=t[x].mid; 58 else t[ls].mid=min(t[ls].mid,t[x].mid); 59 if(t[rs].id==0) t[rs].mid=t[x].mid; 60 else t[rs].mid=min(t[rs].mid,t[x].mid); 61 t[ls].id=1; 62 t[rs].id=1; 63 } 64 } 65 inline void build(int x,int l,int r){ 66 t[x].l=l; 67 t[x].r=r; 68 t[x].mid=0; 69 t[x].id=0; 70 if(l==r) return ; 71 int mi=(l+r)/2; 72 build(ls,l,mi); 73 build(rs,mi+1,r); 74 } 75 inline void insert(int x,int L,int R,int k){ 76 int l=t[x].l,r=t[x].r; 77 push_down(x); 78 if(L<=l&&R>=r){ 79 if(t[x].id==0) t[x].mid=k; 80 else t[x].mid=min(t[x].mid,k); 81 t[x].id=1; 82 return ; 83 } 84 int mid=(l+r)/2; 85 if(L<=mid) insert(ls,L,R,k); 86 if(R>mid) insert(rs,L,R,k); 87 } 88 inline int query(int x,int pos){ 89 int l=t[x].l,r=t[x].r; 90 push_down(x); 91 if(l==r){ 92 if(t[x].id==0) return -1; 93 else return t[x].mid; 94 } 95 int mid=(l+r)/2,ans=0; 96 if(mid>=pos) ans=query(ls,pos); 97 else ans=query(rs,pos); 98 return ans; 99 } 100 inline void solve(int x,int y,int z){ 101 while(top[x]!=top[y]){ 102 if(dep[top[x]]<dep[top[y]]) swap(x,y); 103 insert(1,be[top[x]],be[x],z); 104 x=fa[top[x]]; 105 } 106 if(x==y) return ; 107 if(dep[x]<dep[y]) swap(x,y); 108 insert(1,be[y]+1,be[x],z); 109 } 110 signed main(){ 111 n=read(); 112 m=read(); 113 for(int i=1;i<n;i++){ 114 int x,y; 115 x=read(); 116 y=read(); 117 add(x,y); 118 add(y,x); 119 } 120 build(1,1,n); 121 dfs1(1,0); 122 dfs(1,1); 123 for(int i=1;i<=m;i++){ 124 int x,y,z; 125 x=read(); 126 y=read(); 127 z=read(); 128 solve(x,y,z); 129 } 130 for(int i=1;i<=n;i++){ 131 ans[pre[i]]=query(1,be[i]); 132 } 133 for(int i=1;i<=n-1;i++){ 134 cout<<ans[i]<<"\n"; 135 } 136 return 0; 137 }
T3:明日之星
Problem:
n 位舞台少女各自有一个番号,番号是由‘A’、‘C’、‘G’、‘T’、‘U’五种字符组成的字符串,某种未知力量导致不同的舞台少女的番号可能相同。
我们把第 i 位舞台少女的番号记作 s[i],且每位舞台少女还会有一个梦想值a[i]。
舞台少女之间互相建立了友好的关系,如果把关系看作边,那么这是一棵无根树。
giraffe 想了一种奇特的点名方式,每次点名它会有一个名单 S,S 也是由‘A’、‘G’、‘C’、‘T’、‘U’五种字符组成的字符串,然后它从第 u 位舞台少女走最短路到第 v 位舞台少女,对于途中经过的每位舞台少女 x(包括 u、v),x 的分数为 番号 s[x]在 S 中出现的次数 * 梦想值 a[x],giraffe 想知道分数之和。
当然,舞台少女们随着心情的变化梦想值也是会改变的。
giraffe:“I see.”
谁也不知道 giraffe 知道的分数和是多少,所以拜托你了。
由于 gireffe 喜欢未知的舞台,所以本题强制在线。
第一行两个正整数 n,tp,表示一共有 n 位舞台少女,tp 是强制在线参数,tp=0 或 1。
接下来 n 行,每行一个非空字符串 s[i],表示第 i 位舞台少女的番号,s[i]由‘A’、‘G’、‘C’、‘T’、‘U’五种字符组成。
接下来一行 n 个正整数,第 i 个整数 a[i]表示第 i 位少女一开始的梦想值。
再接下来 n-1 行,每行两个数 x,y,表示第 x 位舞台少女和第 y 位舞台少女有友好的关系。
一行一个整数 Q,表示一共有 Q 次询问或修改。
最后 Q 行,每一行第一个整数 op,表示操作类型。
若 op=1,表示这次操作为询问,接下来 2 个数u_,v_,真正的u=u_xor(lastans*tp),v=v_ xor (lastans*tp),和一个字符串 S,含义如题。
若 op=2,表示这次操作为修改,接下来两个数x_,c_,真正的x=x_xor(lastans*tp),c=c_ xor(lastans*tp),表示第 x 位舞台少女梦想值变为 c 了。
lastans 表示上一次询问的答案,初值为 0.
Solution:
图论、数据结构、字符串综合题
- 1
随机树下路径长度期望是log n的,显然直接暴力把路径上的点全部弄出来KMP就行了。
时间复杂度O(|S|*log n)
- 2
树是链,查询只是1->n,且没有修改。
那么先把所有的串s建个AC自动机,预处理AC自动机上每个点往fail链跳会遇到多少个结尾的点,然后对于每个询问丢进AC自动机跑就好了。
复杂度O(|s|+|S|)
- 3
树是链,查询只是1->n,有修改
还是建出AC自动机,AC自动机fail反链会形成一棵树。对于每个修改相当于把对应的结尾的点的fail反链树下的子树加(减)一个数。
于是求出fail反链树的dfs序,那么子树会在一个区间里,树状数组维护,复杂度O((Q + |S|)log |s|)
- 4
树是链,查询任意,无修改。
做法1:对于2号点的做法直接加上一个不带修主席树。
做法2:用线段树分治结构去维护AC自动机,即线段树每个点代表一个区间,把这个区间里的点代表的串建AC自动机,然后和2号点的做法一样
复杂度都是一个log的。
- 5
树是链,查询任意,有修改,强制在线。
结合3、4号点的做法可以得到两个做法:
1.带修主席树
2.线段树分治结构建AC自动机+树状数组
复杂度都是log^2
发现5号点的做法1本质是维护三维偏序,所以不强制在线的话也可以cdq分治
100:结合5号点算法2即可得到满分。
Code:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=2e5+5; 4 const int maxm=4e5+5; 5 const int maxp=1e7; 6 int n,tp,Q,op,x,y,a[maxn],w[18][maxn],dep[maxn]; 7 char str[maxm],s[maxm]; 8 int len,ls[maxn],rs[maxn]; 9 struct node{ 10 int head[maxn],to[maxn*2],nxt[maxn*2],tot; 11 void add(int x,int y){ 12 nxt[++tot]=head[x]; 13 to[tot]=y; 14 head[x]=tot; 15 } 16 }e; 17 struct node2{ 18 int head[maxp],to[maxm],nxt[maxm],tot; 19 void add(int x,int y){ 20 nxt[++tot]=head[x]; 21 to[tot]=y; 22 head[x]=tot; 23 } 24 }e2; 25 struct node3{ 26 int x,y; 27 }b[maxn*2]; 28 int p[maxn],q[maxn],tt; 29 int ch(char c){ 30 if(c=='A') return 0; 31 if(c=='G') return 1; 32 if(c=='C') return 2; 33 if(c=='T') return 3; 34 return 4; 35 } 36 inline int lowbit(int x){ 37 return x&-x; 38 } 39 inline void dg(int x){ 40 dep[x]=dep[w[0][x]]+1; 41 p[x]=++tt; 42 b[tt].x=x; 43 b[tt].y=1; 44 for(int i=e.head[x];i;i=e.nxt[i]){ 45 if(!p[e.to[i]]){ 46 w[0][e.to[i]]=x; 47 dg(e.to[i]); 48 } 49 } 50 q[x]=++tt; 51 b[tt].x=x; 52 b[tt].y=-1; 53 } 54 inline int lca(int x,int y){ 55 if(dep[x]<dep[y]) swap(x,y); 56 for(int i=17;i>=0;i--){ 57 if(dep[w[i][x]]>=dep[y]){ 58 x=w[i][x]; 59 } 60 } 61 if(x==y) return x; 62 for(int i=17;i>=0;i--){ 63 if(w[i][x]!=w[i][y]){ 64 x=w[i][x]; 65 y=w[i][y]; 66 } 67 } 68 return w[0][x]; 69 } 70 int to[maxp][5],fail[maxp],tc,g[maxn*8],G[maxn*8],d[maxm]; 71 int ed[20][maxn*2],p2[maxp],q2[maxp],pp; 72 inline void dd(int x){ 73 p2[x]=++pp; 74 for(int i=e2.head[x];i;i=e2.nxt[i]){ 75 dd(e2.to[i]); 76 } 77 q2[x]=pp; 78 } 79 int ans,f[maxp]; 80 inline void bt(int i,int x,int y,int dp){ 81 g[i]=++tc; 82 for(int j=x;j<=y;j++){ 83 int p=g[i]; 84 for(int k=ls[b[j].x];k<=rs[b[j].x];k++){ 85 if(!to[p][s[k]]) to[p][s[k]]=++tc; 86 p=to[p][s[k]]; 87 } 88 ed[dp][j]=p; 89 } 90 G[i]=tc; 91 for(int j=1;j<=e2.tot;j++){ 92 e2.nxt[j]=0; 93 } 94 e2.tot=0; 95 for(int j=0;j<=4;j++){ 96 to[0][j]=g[i]; 97 } 98 d[d[0]=1]=g[i]; 99 for(int j=1;j<=d[0];j++){ 100 int x=d[j]; 101 if(j>1) e2.add(fail[x],x); 102 for(int k=0;k<=4;k++){ 103 if(to[x][k]){ 104 int y=to[x][k],p=fail[x]; 105 while(!to[p][k]) p=fail[p]; 106 fail[y]=to[p][k]; 107 d[++d[0]]=y; 108 } 109 } 110 } 111 dd(g[i]); 112 for(int j=x;j<=y;j++){ 113 f[p2[ed[dp][j]]]+=a[b[j].x]*b[j].y; 114 if(q2[ed[dp][j]]<G[i]){ 115 f[q2[ed[dp][j]]+1]-=a[b[j].x]*b[j].y; 116 } 117 } 118 int q=G[i]-g[i]+1; 119 for(int j=1;j<=q;j++){ 120 if(j+lowbit(j)<=q){ 121 f[g[i]+j+lowbit(j)-1]+=f[g[i]+j-1]; 122 } 123 } 124 if(x==y) return ; 125 int mid=(x+y)>>1; 126 bt(i<<1,x,mid,dp+1); 127 bt(i<<1|1,mid+1,y,dp+1); 128 } 129 int pl,pr,px; 130 inline void add(int x,int n,int st,int c){ 131 x-=--st; 132 while(x+st<=n){ 133 f[x+st]+=c; 134 x+=lowbit(x); 135 } 136 } 137 inline void ff(int x,int st){ 138 x-=--st; 139 while(x){ 140 ans+=f[x+st]; 141 x-=lowbit(x); 142 } 143 } 144 inline void xiu(int i,int x,int y,int dp){ 145 if(y<pl||x>pr) return ; 146 add(p2[ed[dp][pl]],G[i],g[i],px); 147 add(q2[ed[dp][pl]]+1,G[i],g[i],-px); 148 if(x==y) return ; 149 int mid=(x+y)>>1; 150 xiu(i<<1,x,mid,dp+1); 151 xiu(i<<1|1,mid+1,y,dp+1); 152 } 153 char t[maxm]; 154 int lt; 155 inline void fi(int i,int x,int y,int dp){ 156 if(y<pl||x>pr) return ; 157 if(x>=pl&&y<=pr){ 158 int p=g[i]; 159 for(int j=0;j<=4;j++){ 160 to[0][j]=p; 161 } 162 for(int j=1;j<=lt;j++){ 163 while(!to[p][t[j]]) p=fail[p]; 164 p=to[p][t[j]]; 165 ff(p2[p],g[i]); 166 } 167 return ; 168 } 169 int mid=(x+y)>>1; 170 fi(i<<1,x,mid,dp+1); 171 fi(i<<1|1,mid+1,y,dp+1); 172 } 173 int main(){ 174 scanf("%d%d",&n,&tp); 175 for(int i=1;i<=n;i++){ 176 scanf("%s",str+1); 177 len=strlen(str+1); 178 ls[i]=rs[i-1]+1; 179 rs[i]=rs[i-1]+len; 180 for(int j=ls[i];j<=rs[i];j++){ 181 s[j]=ch(str[j-ls[i]+1]); 182 } 183 } 184 for(int i=1;i<=n;i++){ 185 scanf("%d",&a[i]); 186 } 187 for(int i=1;i<=n-1;i++){ 188 scanf("%d%d",&x,&y); 189 e.add(x,y); 190 e.add(y,x); 191 } 192 dg(1); 193 for(int i=1;i<=17;i++){ 194 for(int j=1;j<=n;j++){ 195 w[i][j]=w[i-1][w[i-1][j]]; 196 } 197 } 198 bt(1,1,tt,0); 199 scanf("%d",&Q); 200 for(int ii=1;ii<=Q;ii++){ 201 scanf("%d%d%d",&op,&x,&y); 202 x^=ans*tp; 203 y^=ans*tp; 204 if(op==1){ 205 scanf("%s",t+1); 206 lt=strlen(t+1); 207 for(int j=1;j<=lt;j++){ 208 t[j]=ch(t[j]); 209 } 210 int z=lca(x,y); 211 ans=0; 212 pl=p[z]; 213 pr=p[x]; 214 fi(1,1,tt,0); 215 pl=p[z]+1; 216 pr=p[y]; 217 fi(1,1,tt,0); 218 printf("%d\n",ans); 219 } 220 else{ 221 pl=pr=p[x]; 222 px=y-a[x]; 223 xiu(1,1,tt,0); 224 pl=pr=q[x]; 225 px=-px; 226 xiu(1,1,tt,0); 227 a[x]=y; 228 } 229 } 230 return 0; 231 }

浙公网安备 33010602011771号