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.

posted @ 2022-08-02 15:11  Maaaaax  阅读(40)  评论(1)    收藏  举报