AtCoder Regular Contest 097

本文解析了六道经典算法题目,包括后缀自动机、并查集、DP等技术的应用,提供了完整的题解思路及代码实现。

097


 

C - K-th Substring

题意:好像是查询子串第k小。

题解:后缀自动机,反正数据小,可能还有更简单的做法把。

代码:

# include <iostream>
# include <cstring>
# include <cstdio>
using namespace std;
const int N = 1e5 + 12;
const int M = 2e5 + 12;
char str[N];
int cur,cnt,last,nex[M][26],fa[M],dis[M],ch[M][26],l[M];
void build(int c,int id){
    last = cur;cur = ++cnt;
    int u = last;dis[cur] = id;
    while(u &&!nex[u][c])nex[u][c] = cur,u = fa[u];
    if(!u)fa[cur] = 1;
    else {
        int v = nex[u][c];
        if(dis[v] == dis[u] + 1)fa[cur] = v;
        else {
            int nv = ++cnt;dis[nv] = dis[u] + 1;
            memcpy(nex[nv],nex[v],sizeof nex[v]);
            fa[nv] = fa[v];fa[v] = fa[cur] = nv;
            while(u && nex[u][c] == v)nex[u][c] = nv,u = fa[u];
        }
    }
}
int len,n,Q,x,now,size;
int bac[N],y[M],ans[N],f[M];
int main(){
    scanf("%s",str);
    len = strlen(str);
    cnt = cur = 1;
    for(int i = 0;i < len;i++)build(str[i] - 'a',i + 1);
    for(int i = 1;i <= cnt;i++)bac[dis[i]]++;
    for(int i = 1;i <= len;i++)bac[i] += bac[i - 1];
    for(int i = cnt;i >= 1;i--)y[bac[dis[i]]--] = i;
    for(int i = 1;i <= cnt;i++)f[i] = 1;
    for(int i = 1;i <= cnt;i++){
        for(int j = 0;j < 26;j++){
            if(nex[i][j])ch[i][++l[i]] = j;
        }
    }
    for(int i = cnt;i >= 1;i--){
        for(int j = 1;j <= l[y[i]];j++){
            f[y[i]] += f[nex[y[i]][ch[y[i]][j]]];
        }
    }
        scanf("%d",&x);
        len = 0;now = 1;size = 0;
        while(x){
            for(int i = 1;i <= l[now];i++){
                if(f[nex[now][ch[now][i]]] < x)x -= f[nex[now][ch[now][i]]];
                else {
                    x--;
                    str[size++] = 'a' + ch[now][i];
                    now = nex[now][ch[now][i]];
                    break;
                }
            }
        }
        str[size] = '\0';
        puts(str);
}
097C

D - Equals

题意:有一个全排列,然后有m种交换方式,问经过数次交换能使在i的位置上的数为i的个数最大值

题解:并查集裸题

代码:

# include <iostream>
# include <cstdio>
using namespace std;
const int N = 2e5 + 12;
int fa[N],n,m,a[N],in[N],ans;
int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);}
int main(){
  scanf("%d %d",&n,&m);
  for(int i = 1;i <= n;i++)scanf("%d",&a[i]),in[a[i]] = fa[i] = i;
  int x,y;
  for(int i = 1;i <= m;i++)scanf("%d %d",&x,&y),fa[find(y)] = find(x);
  for(int i = 1;i <= n;i++)if(find(in[i]) == find(i))ans++;
  printf("%d\n",ans);
}
097D

E - Sorted and Sorted

题意:有黑球白球全排列(1~n)混在了一起,变成了一个长度为2n的数组,问以交换相邻两数的方式给黑白球排序,只使黑白各自内部有序即可,问最少操作次数

题解:如果只有一个颜色就是逆序数了,两种也是这种贪心思想,但是要看在某个时间是先排白球优还是黑球优,套个dp即可

定义dp[i][j]表示已经使白球的1 - i和黑球的1 - j有序所需要的最少操作次数,

预处理出black[i][j] 表示在i位置前大于j的黑球有多少个,white[i][j]表示在i位置前大于j的白球有多少个

pos1表示编号为i白球所在位置,pos2表示编号为j黑球所在位置

那么 dp[i][j] = min(dp[i - 1][j] + white[pos1][i - 1] + black[pos1][j],dp[i][j - 1] + white[pos2][i] + black[pos2][j - 1])

最后输出dp[n][n]即可。

代码:

# include <iostream>
# include <cstdio>
using namespace std;
const int N = 4e3 + 12;
int a[N],b[N],black[N][N],white[N][N],n,bpos[N],wpos[N],dp[N][N];
int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n << 1;i++)
    {
        char ch[2];
        scanf("%s %d",ch,&a[i]);b[i] = ch[0];
        if(b[i] == 'B')bpos[a[i]] = i;
        else wpos[a[i]] = i;
    }
    for(int i = 1;i <= n << 1;i++)
     for(int j = 0;j <= n;j++)
     {
         black[i][j] = black[i - 1][j] + (a[i - 1] > j && b[i - 1] == 'B'); 
         white[i][j] = white[i - 1][j] + (a[i - 1] > j && b[i - 1] == 'W');
     }
     for(int i = 0;i <= n;i++) 
      for(int j = 0;j <= n;j++)
      {
          if(!i && !j)continue;
          int x = bpos[i],y = wpos[j];
          dp[i][j] = 0x3f3f3f3f;
          if(i)dp[i][j] = min(dp[i][j],dp[i - 1][j] + black[x][i - 1] + white[x][j]);
          if(j)dp[i][j] = min(dp[i][j],dp[i][j - 1] + black[y][i] + white[y][j - 1]);
      }
      printf("%d\n",dp[n][n]);
}
097E

F - Monochrome Cat

题意:有只猫在树上任意起点,可以执行两种操作:

1,走一条边,并使终点颜色反转

2,反转当前点颜色

问使所有点颜色变成黑色的最小时间

题解:

首先把图简化下,黑色叶子结点是没用的,我们走到它一定不优,所以我们简化到图只剩白色叶子结点

考虑起点和终点相同的情况,无论在起点是任何点,每条边都会经过两次(经过三次一定不优)。那么每个点被第二种操作反转次数为它的度数

那么我们花费时间为2 * (点数 - 1) + (计算反转后为白色的点)

再考虑在这种情况下选择一条路径s-t,发现除了终点t,每个点经过次数都减少了1.(即路径s - t的边只经过一次)

那么我们的答案变成了2 * (点数 - 1) + (计算反转后为白色的点) -  ((s - t路径上除t外计算反转后为白色的点)- (s - t路径上除t外上计算反转为黑色的点) + 链长)

令后面大括号为f[i]

从一个白色叶子节点为根,做树形dp,选出最优链即可。

f[i][0]表示以i的子树内(包括i)有一个起点s,且s 到 i这条链的最大f[i]

f[i][0]表示以i的子树内(包括i)有一个终点t,且i 到 t这条链的最大f[i]

dp即可。

代码:

# include <iostream>
# include <cstdio>
using namespace std;
const int N = 2e5 + 12;
const int inf = 0x3f3f3f3f;
int n,head[N],dt,du[N],bac[N],que[N],rt,a[N],tot,p;char str[N];bool w[N];
struct Edge{int to,nex;}edge[N << 1];
void Add(int u,int v){edge[++dt] = (Edge){v,head[u]};head[u] = dt;du[u]++;}
void Dfs(int u,int pre)
{
    if(str[u] == 'W')w[u] = true;
    for(int i = head[u];i;i = edge[i].nex)if(edge[i].to != pre)
    {
        Dfs(edge[i].to,u);
        if(!w[edge[i].to])du[u]--,du[edge[i].to]--;
        w[u] |= w[edge[i].to];
    }
    if(w[u])tot++;
}
void rebuild()
{
    for(int i = 1;i <= n;i++)if(str[i] == 'W')rt = i;
    Dfs(rt,-1);
    for(int i = 1;i <= n;i++)if(w[i])a[i] = (str[i] == 'B') ^ (du[i] & 1),p += !a[i];
}
int dp[N][2],f[N];
void dfs(int u,int pre)
{
    f[u] = (tot - 1) * 2 + p;
    dp[u][0] = a[u] ? -1 : 1;
    for(int i = head[u];i;i = edge[i].nex)if(edge[i].to != pre && w[edge[i].to])
    {
        dfs(edge[i].to,u);
        f[u] = min(f[u],f[edge[i].to]);
        f[u] = min(f[u],(tot - 1) * 2 + p - dp[u][0] - dp[edge[i].to][1] - 1);
        f[u] = min(f[u],(tot - 1) * 2 + p - dp[u][1] - dp[edge[i].to][0] - 1);
        dp[u][0] = max(dp[u][0],dp[edge[i].to][0] + 1 + (a[u] ? -1 : 1));
        dp[u][1] = max(dp[u][1],dp[edge[i].to][1] + 1 + (a[u] ? -1 : 1));
    }
}
int main()
{
    
    scanf("%d",&n);int x,y;
    for(int i = 1;i < n;i++)scanf("%d %d",&x,&y),Add(x,y),Add(y,x);
    scanf("%s",str + 1);rebuild();dfs(rt,-1);
    printf("%d\n",!tot ? tot : f[rt]);
}
097F

 

转载于:https://www.cnblogs.com/lzdhydzzh/p/9178754.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值