[[https://codeforces.com/group/2g1PZcsgml/contest/337661|比赛链接]] ====== 题解 ====== ===== C. Cover the Paths ===== ==== 题意 ==== 给定一棵树和若干路径,要求选出最小的点集,使得每条路径上至少有一个点。 ==== 题解 ==== 强制以 $1$ 为根,根据每条路径两端点的 $\text{LCA}$ 深度从大到小排序。 对于 $\text{LCA}$ 深度最大的路径,显然必须选择一个点,由于其他路径的 $\text{LCA}$ 深度都不小于该路径,显然选择该路径的 $\text{LCA}$ 最优。 然后再考虑深度第二大的点,如果该路径上已经有点被选就不再选点,否则再选一个 $\text{LCA}$,不断贪心。 贪心正确性是由于该决策顺序下路径 $\text{LCA}$ 深度递增,所以之前选的点深度越小越好,因此前面的路径的点只选 $\text{LCA}$,且尽量少选。 const int MAXN=1e5+5; #define lowbit(x) ((x)&(-x)) namespace Tree{ int c[MAXN]; void add(int pos){ while(posmson[u]){ h_son[u]=v; mson[u]=sz[v]; } } } void dfs_2(int u,int top){ dfn[u]=++dfs_t,p[u]=top; if(mson[u]) dfs_2(h_son[u],top); for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; if(v==f[u]||v==h_son[u]) continue; dfs_2(v,v); } } int query(int u,int v){ int ans=0; while(p[u]!=p[v]){ if(d[p[u]]d[v]) swap(u,v); ans+=Tree::query(dfn[u],dfn[v]); return ans; } int lca(int u,int v){ while(p[u]!=p[v]){ if(d[p[u]]d[b.p]; } }node[MAXN]; int main(){ int n=read_int(); _for(i,1,n){ int u=read_int(),v=read_int(); Insert(u,v); Insert(v,u); } dfs_1(1,0,0); dfs_2(1,1); int m=read_int(); _for(i,0,m){ int u=read_int(),v=read_int(),p=lca(u,v); node[i]=Node{u,v,p}; } sort(node,node+m); vector ans; _for(i,0,m){ if(query(node[i].u,node[i].v)==0){ ans.push_back(node[i].p); Tree::add(dfn[node[i].p]); } } enter(ans.size()); for(int u:ans) space(u); return 0; } ===== G. Berland Post ===== ==== 题意 ==== 给定若干条形如 $x_v+b_i\le x_u+T$ 的限制,求最小的 $T$ 以及这种情况下的一组合法解 $(x_1,x_2\cdots x_n)$。 其中,某些 $x_i$ 的值已经固定。 ==== 题解 ==== 将方程转化为 $x_v\le x_u+T-b_i$,考虑二分 $T$ 然后差分约束求解。 设超级源点为 $s$。关于点权的限制,对于无限制的 $x_i$,连边 $(s,i,\infty),(i,s,\infty)$,表示 $-\infty\le x_i\le \infty$。 对于固定的 $x_i$,连边 $(s,i,v),(i,s,-v)$,表示 $v\le x_i\le v$。时间复杂度 $O(nm\log V)$。 const int MAXN=1e3+5,MAXM=4e3+5,SPV=2e5; const double inf=1e15,infv=1e7,eps=1e-7,eps2=1e-9; int read_int(){ int t=0;bool sign=false;char c=getchar(); while(c==' '||c=='\r'||c=='\n')c=getchar(); if(c=='?')return SPV; if(c=='-')sign=true,c=getchar(); while(isdigit(c)){ t=t*10+c-'0'; c=getchar(); } return sign?-t:t; } struct Edge{ int to,next; double w; }edge[MAXM<<1]; int head[MAXN],edge_cnt; void Insert(int u,int v,double w){ ++edge_cnt; edge[edge_cnt].to=v; edge[edge_cnt].w=w; edge[edge_cnt].next=head[u]; head[u]=edge_cnt; } namespace SPFA{ double dis[MAXN]; int len[MAXN]; bool inque[MAXN]; bool solve(int src,int n){ queueq; _rep(i,1,n){ inque[i]=false; len[i]=0; dis[i]=inf; } dis[src]=0;len[src]=1; q.push(src); inque[src]=true; while(!q.empty()){ int u=q.front();q.pop(); inque[u]=false; for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; if(dis[v]>dis[u]+edge[i].w+eps2){ dis[v]=dis[u]+edge[i].w; len[v]=len[u]+1; if(len[v]>n) return false; if(!inque[v]){ q.push(v); inque[v]=true; } } } } return true; } } struct Edge2{ int u,v,w; }; vector edges; int a[MAXN]; bool check(int n,double T){ int s=++n; edge_cnt=0; _rep(i,1,n) head[i]=0; _for(i,1,n){ if(a[i]==SPV){ Insert(s,i,infv); Insert(i,s,infv); } else{ Insert(s,i,a[i]); Insert(i,s,-a[i]); } } for(Edge2 e:edges) Insert(e.v,e.u,T-e.w); return SPFA::solve(s,n); } int main(){ int n,m; while(cin>>n>>m){ edges.clear(); _rep(i,1,n) a[i]=read_int(); while(m--){ int u=read_int(),v=read_int(),w=read_int(); edges.push_back(Edge2{u,v,w}); } double lef=0,rig=infv; while(rig-lef>eps){ double mid=(lef+rig)/2; if(check(n,mid)) rig=mid; else lef=mid; } printf("%.6lf\n",lef); check(n,lef); _rep(i,1,n) printf("%.6lf ",SPFA::dis[i]); puts(""); } return 0; } ===== J. Subsequence Sum Queries ===== ==== 题意 ==== 给定一个长度为 $n$ 的序列,接下来 $q$ 个询问,每次询问区间 $[l,r]$ 有多少个子序列满足元素之和整除 $m$。 ==== 题解 ==== 考虑分治处理询问。设当前维护区间为 $[lef,rig]$,$mid=\frac {lef+rig}2$。对 $[l,r]\in [lef,mid],[mid+1,rig]$ 的询问直接递归到左右区间处理。 对于跨 $\text{mid}$ 的询问,提前 $O\left((rig-lef)m\right)$ 处理出区间 $[i,mid](lef\le i\le mid),[mid+1,j](mid+1\le j\le rig)$ 的答案。 然后对每个询问相当于背包合并,时间复杂度 $O(qm^2)$。于是总时间复杂度 $O(nm\log n+qm^2)$。 const int MAXN=2e5+5,MAXM=20,mod=1e9+7; int ans[MAXN],a[MAXN],m; struct query{ int lef,rig,id; }; int s[MAXN][MAXM],temp[MAXM<<1]; void solve(int lef,int rig,vector b){ int mid=lef+rig>>1; if(lef==rig){ _for(i,0,b.size()) ans[b[i].id]=1+(a[mid]==0); return; } mem(s[mid],0); s[mid][0]++; s[mid][a[mid]]++; for(int i=mid-1;i>=lef;i--){ _for(j,0,m) s[i][(a[i]+j)%m]=(s[i+1][(a[i]+j)%m]+s[i+1][j])%mod; } mem(s[mid+1],0); s[mid+1][0]++; s[mid+1][a[mid+1]]++; for(int i=mid+2;i<=rig;i++){ _for(j,0,m) s[i][(a[i]+j)%m]=(s[i-1][(a[i]+j)%m]+s[i-1][j])%mod; } vectorb1,b2; _for(i,0,b.size()){ if(b[i].rig<=mid) b1.push_back(b[i]); else if(b[i].lef>mid) b2.push_back(b[i]); else{ mem(temp,0); _for(j,0,m)_for(k,0,m) temp[j+k]=(temp[j+k]+1LL*s[b[i].lef][j]*s[b[i].rig][k])%mod; ans[b[i].id]=(temp[0]+temp[m])%mod; } } solve(lef,mid,b1); solve(mid+1,rig,b2); } int main() { int n=read_int(); m=read_int(); _rep(i,1,n)a[i]=read_LL()%m; int q=read_int(); vector b; _for(i,0,q){ int l=read_int(),r=read_int(); b.push_back(query{l,r,i}); } solve(1,n,b); _for(i,0,q) enter(ans[i]); return 0; } ===== L. Increasing Costs ===== ==== 题意 ==== 给定一个无向连通图,定义源点为 $1$ 号点。对每条边,询问删除该边会导致源点到多少个点的最短路改变。 ==== 题解 ==== 首先跑最短路,然后保留所有在最短路树上的边,同时规定每条边方向由距离近的点指向距离远的点,易知构成有向无环图。 问题转化为求有向无环图的支配边。 对任意一个点,如果该点有至少两条入边,易知所有入边支配点集均为空,否则该边的支配点集等价于该点的支配子树。 const int MAXN=2e5+5,MAXM=2e5+5,MAXV=22; namespace Tree{ struct Edge{ int to,id,next; }edge[MAXN+MAXM]; int head1[MAXN],head2[MAXN],edge_cnt; int deg[MAXN],f[MAXN],dep[MAXN],anc[MAXN][MAXV],lg2[MAXN]; int deg0[MAXN]; void Insert1(int u,int v,int id){ edge[++edge_cnt]=Edge{v,id,head1[u]}; head1[u]=edge_cnt; deg[v]++; deg0[v]++; } void Insert2(int u,int v){ edge[++edge_cnt]=Edge{v,0,head2[u]}; head2[u]=edge_cnt; } int LCA(int u,int v){ if(dep[u]dep[v])u=anc[u][lg2[dep[u]-dep[v]]]; if(u==v) return u; for(int i=MAXV-1;i>=0;i--){ if(anc[u][i]!=anc[v][i]) u=anc[u][i],v=anc[v][i]; } return anc[u][0]; } int build(int n){ lg2[1]=0; _for(i,2,MAXN)lg2[i]=lg2[i>>1]+1; int rt=n+1; queue q; _rep(i,1,n){ if(deg[i]==0){ f[i]=rt; q.push(i); } } while(!q.empty()){ int u=q.front();q.pop(); dep[u]=dep[f[u]]+1; Insert2(f[u],u); anc[u][0]=f[u]; for(int i=1;i >q; mem(dis,127); dis[1]=0; q.push(make_pair(-dis[1],1)); while(!q.empty()){ int u=q.top().second; q.pop(); if(vis[u])continue; vis[u]=true; for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; if(dis[v]>dis[u]+edge[i].w){ dis[v]=dis[u]+edge[i].w; q.push(make_pair(-dis[v],v)); } } } _rep(u,1,n){ for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; if(dis[v]==dis[u]+edge[i].w) Tree::Insert1(u,v,edge[i].id); } } Tree::solve(n,m); return 0; } ====== 赛后总结 ====== jxm:开局一个多小时后就罚坐了,疯狂写假题,下次一定先确认思路没问题再写题。