第十七届四川省赛vp记录

solved 5 problems
\(I\)\(F\) : @xmjlove
\(H\)\(J\)\(K\)@dbywsc

I.本质不同后缀

思路

使用字典树维护。将所有字符串倒过来,录入前缀树,树的大小即为本质不同后缀的个数。

public static void main(String[] args) throws Exception{
    int n=sc.nextInt();
    tire=new int[300010][30];
    for(int i=1;i<=n;i++){
        String s=sc.next();
        add(s);
    }
    System.out.println(size);
}
public static int[][] tire;
public static int size=0;
public static void add(String s){
    int node=0;
    for(int i=s.length()-1;i>=0;i--){
        int id=s.charAt(i)-'a';
        if(tire[node][id]==0)tire[node][id]=++size;
        node=tire[node][id];
	}
}

H. 胡图图

思路

由于最多可以走 \((x \pm 2, y \pm 2)\) 的距离,因此我们考虑尽可能的一次走两步。
将二维方向拆开考虑,计算横坐标移动的距离 \(dx\) 和 纵坐标移动的距离 \(dy\)
接下来分情况讨论:
如果 \(dx = dy\) ,那么每次都走 \((\pm 2, \pm 2)\) 就好了,如果是奇数,最后还要补一个 \((\pm 1, \pm 1)\) ,因此总次数为 \(\lceil \frac{2}{dx} \rceil\)
否则,当出现一维是 \(0\) ,另一维小于等于 \(2\) 时,无论如何都要花两次才能走到。
否则,对于更大的那一维,每次尽可能的走 \(\pm 2\) ,另一维可以通过每次走 \(\pm 1\) 或者 \(0\) 缩小两维之间的差距。因此答案为 \(\lceil \frac{2}{\max(dx, dy)} \rceil\)

代码

void solve(void) {
    i64 x, y, X, Y;
    std::cin >> x >> y >> X >> Y;
    i64 dx = std::llabs(x - X), dy = std::llabs(y - Y);
    if(dx == 0 && dy == 0) std::cout << 0 << endl;
    else {
        if(dx == dy) std::cout << (dx + 1) / 2 << endl;
        else {
            i64 m = std::max(dx, dy), n = std::min(dx, dy);
            if(n == 0 && m <= 2) std::cout << 2 << endl;
            else {
                std::cout << (m + 1) / 2 << endl;
            }
        }
    }
}

J.四川省赛

思路

显然,直接使用 \(DFS\) 搜五个点的方案会超时。
考虑从中间的 `C` 出发,这样每次只需要找左右两边的 `SC` 和 `PC` 的方案数就行了,分别记作 \(pre_i\)\(suf_i\),根据乘法原理,贡献应该为 \(pre_i \times suf_i\)

代码

std::vector<int> G[N];
void solve(void) {
    int n; std::cin >> n;
    for(int i = 0; i <= n; i++) G[i].clear();
    std::vector<char> w(n + 1);
    for(int i = 1; i <= n; i++) {
        std::cin >> w[i];
    }
    for(int i = 1; i < n; i++) {
        int u, v; std::cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    i64 ans = 0;
    std::vector<i64> cntS(n + 1), cntC(n + 1);
    for(int i = 1; i <= n; i++) cntS[i] = cntC[i] = 0;
    for(int u = 1; u <= n; u++) {
        for(auto v : G[u]) {
            if(w[v] == 'S') cntS[u]++;
            if(w[v] == 'C') cntC[u]++;
        }
    }
    std::vector<i64> pre(n + 1), suf(n + 1);
    for(int i = 1; i <= n; i++) pre[i] = suf[i] = 0;
    for(int v = 1; v <= n; v++) {
        if(w[v] != 'C') continue;
        i64 cnt = cntS[v];
        if(cnt == 0) continue;
        for(auto x : G[v]) {
            if(w[x] == 'C') pre[x] += cnt;
        }
    }
    for (int u = 1; u <= n; u++) {
        if(w[u] != 'P') continue;
        int c = cntC[u];
        if(c < 2) continue;
        for(int x : G[u]) {
            if(w[x] == 'C') suf[x] += (c - 1);
        }
    }
    for(int i = 1; i <= n; i++) {
        if(w[i] == 'C') ans += pre[i] * suf[i];
    }
    std::cout << ans << endl;
}

F.逆序对

思路

如果当前位置为 \(0\) ,答案会加上当前位置左边 \(1\) 的数量;如果为 \(1\) ,会为所有后面的 \(0\) 做贡献。
\(l\) 数组记录每个位置左边 \(1\) 的数量,\(r\) 数组记录每个位置右边 \(0\)? 的数量

如果当前位置为 \(i\) ,且 \(s_i = ?\) ,那么比较 \(l_i\)\(r_i\) 的大小,如果能为右面做的贡献不如直接加上左边 \(1\) 数量后的贡献,当前位置设置为 \(1\) ,反之为 \(0\)

\(r\) 数组记录 ? 的数量,即假定他为 \(0\) ,如果他设置为 \(1\),会至多减少 \(l_i\) 的贡献,但加上 \(r_i\) 的贡献,所以还是最优的

代码

public static void main(String[] args) throws Exception{
    int t=sc.nextInt();
    int[] l=new int[1000010];
    int[] r=new int[1000010];
    int[] cnt=new int[1000010];
    while (t-->0){
        int n=sc.nextInt();
        StringBuilder s=new StringBuilder();
        s.append(sc.next());
        r[n-1]=0;
        for(int i=1;i<n;i++){
            l[i]=l[i-1];
            if(s.charAt(i-1)=='1')l[i]++;
        }
        for(int i=n-2;i>=0;i--){
            r[i]=r[i+1];
            if(s.charAt(i+1)=='0'||s.charAt(i+1)=='?')r[i]++;
        }
        long ans=0;
        for(int i=0;i<n;i++){
            cnt[i+1]=cnt[i];
            l[i]+=cnt[i];
            if(s.charAt(i)=='?'){
                if(l[i]>r[i])s.setCharAt(i,'0');
                else {
                    s.setCharAt(i,'1');
                    cnt[i+1]++;
                }
            }
        }
        for(int i=0;i<n;i++){
            if(s.charAt(i)=='0')ans+=l[i];
        }
        pw.println(ans);
    }
    pw.flush();pw.close();
}

K.点分治

思路

正着去依次删除点后分裂非常难写。但是可以倒着来做,每次相当于是将后面的点加入到图中,此时它会和图中的连通块合并,并且成为这棵有根树的根。因此可以用并查集来维护合并的过程。

代码

std::vector<int> G[N];
void solve(void) {
    int n; std::cin >> n;
    std::vector<int> p(n);
    for(int i = 0; i < n; i++) std::cin >> p[i];
    for(int i = 0; i <= n; i++) G[i].clear();
    for(int i = 1; i < n; i++) {
        int u, v;
        std::cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    std::vector<int> P(n + 1);
    std::vector<bool> vis(n + 1);
    std::vector<int> fa(n + 1);
    for(int i = 1; i <= n; i++) P[i] = i;
    auto find = [&](auto && self, int x) -> int {
        if(P[x] == x) return x;
        return P[x] = self(self, P[x]);
    };
    for(int i = n - 1; i >= 0; i--) {
        int u = p[i];
        vis[u] = 1;
        for(auto v : G[u]) {
            if(!vis[v]) continue;
            int pu = find(find, u);
            int pv = find(find, v);
            if(pu != pv) {
                fa[pv] = u;
                P[pv] = pu;
            }
        }
    }
    for(int i = 1; i <= n; i++) {
        std::cout << fa[i] << " ";
    } std::cout << endl;
}
posted @ 2025-07-13 16:20  dbywsc  阅读(21)  评论(0)    收藏  举报