2022牛客暑期多校第三场
【奇怪的优化】C
【题意】
给 n 个由0,1,2,3,4组成的字符串,求如何排列使他们连起来组成的串字典序最小。$1 \leq n \leq 2 \times 10^6,\sum_{i=1}^n|s_i| \leq 2 \times 10^7$,时限4s。
【题解】
经典问题,但sort函数要用引用传参才不会T,复杂度$O(|s|logn)$。
这很奇怪,因为正解是$O(|s|)$的trie+exkmp,暂时按下不表。
【代码】
#include <bits/stdc++.h> using namespace std; const int N=2e6+5; int n; string s[N]; bool cmp(const string &a,const string &b){ //就是这里 string x=a+b,y=b+a; return x<y; } int main() { scanf("%d",&n); for (int i=1;i<=n;i++) cin>>s[i]; sort(s+1,s+1+n,cmp); for (int i=1;i<=n;i++) cout<<s[i]; return 0; }
【LCA性质分析+dfs / 前后缀LCA】A
【题意】
给两颗 n 个点带点权的树和 k 个关键点,删除这 k 个点中的一个(树结构不变),使得剩下的 k-1 个点在 A 树上的 LCA 的权值比 B 树上的大。求有几种删点方案。$2 \leq k \leq n \leq 10^5$
【题解】
—LCA性质分析+dfs—
L 表示原来所有关键点的 LCA,L' 表示删除一个点后剩下的点的 LCA,分析出如下几种情况:
- 如果存在一个不同于 L 的点 u,是 k-1 个点的 LCA:删除这 k-1 个点中的任意一点,L'=L,删除余下的那个特殊点,L'=u
- 如果不存在上述情况:删除任意一点,L' = L
据此,可以用 dfs 预处理出 L、u 和特殊点(如果存在的话):子树中包含 k 个关键点的最深的点即为 L,子树中包含 k-1 个关键点的最深的点即为 u,特殊点分两种情况——如果关键点在一条链上,L即为特殊点;否则,L 的某个儿子的子树仅包含 1 个关键点,这个关键点即为特殊点。然后可以算出删除每个点后的 L',统计答案。时间复杂度为$O(n)$。
【代码】
w, ans, sz, LCA, newLCA, one, key, cnt 数组第一维为 0 表示 A 树,1 表示 B 树。
x[i] 表示第 i 个关键点的编号,is[i] 表示编号为 i 的点是否为关键点;w 为点权;sz 为子树大小,one 为子树中任意一个关键点的编号,用来找特殊点;cnt 为 L 有多少个儿子的子树中包含关键点,用来判链;LCA 为 L,newLCA 为 u,key 为特殊点;ans 为删除一个点后剩余点的 LCA。
#include <bits/stdc++.h> using namespace std; const int N=1e5+5; int n,k,x[N],w[2][N],t; int ans[2][N],sz[2][N],LCA[2],newLCA[2],one[2][N],key[2],res,cnt[2]; vector<int> out[2][N]; bool is[N]; void dfs(int id,int u){ if (is[u]) sz[id][u]=1,one[id][u]=u; int len=out[id][u].size(); for (int i=0;i<len;i++){ int v=out[id][u][i]; dfs(id,v); sz[id][u]+=sz[id][v]; one[id][u]=one[id][v]; } if (sz[id][u]==k&&LCA[id]==0) LCA[id]=u; //找L if (sz[id][u]==k-1&&newLCA[id]==0) newLCA[id]=u; //找u } void solve(int id){ int len=out[id][LCA[id]].size(); for (int i=0;i<len;i++){ int v=out[id][LCA[id]][i]; if (sz[id][v]==1) key[id]=one[id][v]; //找特殊点 if (sz[id][v]) cnt[id]++; //用于判链 } if (cnt[id]==1) key[id]=LCA[id]; //链的情况 for (int i=1;i<=k;i++){ if (x[i]==key[id]){ if (newLCA[id]==0) ans[id][i]=w[id][LCA[id]]; //如果u不存在 else ans[id][i]=w[id][newLCA[id]]; } else ans[id][i]=w[id][LCA[id]]; } if (k==2){ //k=2需要特判 ans[id][1]=w[id][x[2]]; ans[id][2]=w[id][x[1]]; } } int main() { scanf("%d%d",&n,&k); for (int i=1;i<=k;i++){ scanf("%d",&x[i]); is[x[i]]=1; } for (int i=1;i<=n;i++) scanf("%d",&w[0][i]); for (int i=1;i<n;i++){ scanf("%d",&t); out[0][t].push_back(i+1); } dfs(0,1); solve(0); for (int i=1;i<=n;i++) scanf("%d",&w[1][i]); for (int i=1;i<n;i++){ scanf("%d",&t); out[1][t].push_back(i+1); } dfs(1,1); solve(1); for (int i=1;i<=k;i++) //枚举删除每个点 if (ans[0][i]>ans[1][i]) res++; printf("%d",res); return 0; }
—前后缀LCA—
上面的方法虽然复杂度十分优秀,但情况比较多很容易写砸orz。舍弃复杂度换取简明度的方法是:预处理出前缀 LCA 和后缀 LCA,删除某个点后的 L' 即为前后缀 LCA 的 LCA。时间复杂度$O(nlogn)$。代码略。
【建图+最短路】J
【题意】
有 n 个十字路口,每个路口 8 条道路交汇(有序数对<a,b>表示一条由 a 路口通往 b 路口的道路,<a,b> != <b,a>,由<a,b>走到<b,a>需要在 b 路口掉头)。在路口左转、直行、掉头都需要花费 1 时间,右转不需要。求由<s1,s2>走到<t1,t2>所需最少时间。$2 \leq n \leq 5 \times 10^5$,时限7s。
【题解】
将路看成点,在十字路口处连边,右转边权为0,其余为1,跑最短路,复杂度$O(nlogn)$,常数比较大就是了。
【实现】
对于每个路口输入 4 个整数,表示道路另一端的路口编号,只保证逆时针输入,不保证绝对方向。$c_{i,j}$表示 i 路口的第 j 个输入,则从$<c_{i,j},i>$到 $<i,c_{i,j\%4+1}>$ 为右转。
用 map 会超时,unordered_map 不支持 pair,所以需要先把点对转化成数再哈希,转化成数可以用 a*n+b。
【代码】
#include <bits/stdc++.h> using namespace std; const int N=2000005; int n,c[5],idx,dis[N],s,t,book[N]; struct node{ int u,w; bool operator < (const node &x) const{ return w>x.w; }; }; priority_queue<node> d; unordered_map<long long,int> mp; vector<node> out[N]; long long hs(int a,int b){ //点对转化成数 return (long long)a*(long long)n+(long long)b; } int main() { scanf("%d",&n); for (int i=1;i<=n;i++){ scanf("%d%d%d%d",&c[1],&c[2],&c[3],&c[4]); for (int j=1;j<=4;j++){ //哈希 if (c[j]==0) continue; if (mp[hs(c[j],i)]==0) mp[hs(c[j],i)]=++idx; if (mp[hs(i,c[j])]==0) mp[hs(i,c[j])]=++idx; } for (int j=1;j<=4;j++){ //连边 if (c[j]==0) continue; for (int k=1;k<=4;k++){ if (c[k]==0) continue; if (k==j%4+1) out[mp[hs(c[j],i)]].push_back({mp[hs(i,c[k])],0}); else out[mp[hs(c[j],i)]].push_back({mp[hs(i,c[k])],1}); } } } scanf("%d%d%d%d",&c[1],&c[2],&c[3],&c[4]); s=mp[hs(c[1],c[2])]; t=mp[hs(c[3],c[4])]; for (int i=1;i<=idx;i++) dis[i]=-1; //dijksra dis[s]=0; d.push({s,0}); while (!d.empty()){ int u=d.top().u,len=out[u].size(); d.pop(); if (book[u]) continue; book[u]=1; for (int i=0;i<len;i++){ int v=out[u][i].u; if (dis[v]==-1||dis[v]>dis[u]+out[u][i].w){ dis[v]=dis[u]+out[u][i].w; d.push({v,dis[v]}); } } } printf("%d",dis[t]); return 0; }
【SAM】H
TBC.
【图的双极定向】F
TBC.
【概率dp】D
TBC.
【网络流+问题转化】B
TBC.

浙公网安备 33010602011771号