2023河南萌新赛第五场vp记录

solved 8 problems

A、B、E、G: @dbywsc

I、J、L: @xmjlove

D:@DASJ

A.买爱心气球

思路

对于小气球,可以直接当作普通的 Nim 游戏处理,非 \(0\) 即有必胜态;对于大气球而言,打表观察可以发现,当大气球的数量为 \(3\) 的倍数或者 \(0\) 时,Alice 必败,因此必胜态为 \(m \mod 3 \neq 0\)

所以可以当作变形的 Nim 游戏。 Alice 可以获胜当且仅当 \((m \mod 3) \oplus n \neq 0\)

代码

void solve(void) {
    int m, n; std::cin >> m >> n;
    m %= 3;
    std::cout << ((m ^ n) ? "Alice" : "Bob") << '\n';
}

B.亚托莉 -我挚爱的时光-

思路

这个没什么好说的,模拟。

代码

void solve() {
    int n;
    std::cin >> n;
    std::cin.ignore(); 

    std::map<std::string, int> pac; 

    for (int i = 0; i < n; i++) {
        std::string s;
        std::getline(std::cin, s);

        if (s == "sudo rm -rf /*") {
            std::cout << "wuwuwu\n";
            return;
        }
        if (s.find("sudo pacman -S ", 0) == 0) {
            std::string name = s.substr(15);
            pac[name] = 1;
        }
        else if(s.rfind("pacman -Rscn ", 0) == 0) {
            std::string name = s.substr(13);
            pac[name] = -1;
        }
        else if(s.rfind("pacman -R ", 0) == 0) {
            std::string name = s.substr(10);
            pac[name] = 0;
        }
        else if(s[0] == '1') {
            std::string name = s.substr(2);
            if(pac.count(name) && pac[name] == 1) {
                std::cout << "yes\n";
            } else std::cout << "no\n";
        }
        else if(s[0] == '2') {
            std::string name = s.substr(2);
            if(pac.count(name) && pac[name] != -1) {
                std::cout << "yes\n";
            } else std::cout << "no\n";
        }
    }
}

D.01分数规划

思路

复制两个字符串,一个将问号全部换成1,另一个将问号全部换成0,然后开始遍历这两个字符串,找出最长的1或者0。

代码

#include<bits/stdc++.h>
using namespace std;

int main(){
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        string s;
        cin>>s;
        string s1=s;
        string s2=s;
        for(int i=0;i<n;i++){
            if(s1[i]=='?'){
                s1[i]='0';
            }    
        }
        for(int i=0;i<n;i++){
            if(s2[i]=='?'){
                s2[i]='1';
            }    
        }
        int ans=0;
        int yi=0;
        int ling=0; 
        for(int i=0;i<n;i++){
            if(s1[i]=='1'){
                ans=max(ans,ling);
                ling=0;
                yi++;
            }else if(s1[i]=='0'){
                ans=max(ans,yi);
                yi=0;
                ling++;
            }
        }
        int shu=max(yi,ling);
        ans=max(ans,shu); 
        yi=0;
        ling=0;
        for(int i=0;i<n;i++){
            if(s2[i]=='1'){
                ans=max(ans,ling);
                ling=0;
                yi++;
            }else if(s2[i]=='0'){
                ans=max(ans,yi);
                yi=0;
                ling++;
            }
        }
        shu=max(yi,ling);
        ans=max(ans,shu);
        cout<<ans<<endl;
    }

    return 0;
}

E.换根DP

思路

由于边权一定是 \(1\)\(2\) ,因此两点距离为 \(2\) 当且仅当路径之间的不存在权为 \(1\) 的边。因此可以删掉边权为 \(1\) 的边,此时树就变成了多个连通块。显然 \(min(f(i))\) 存在于最小的连通块中。

代码

struct DSU {
    std::vector<int> p, siz;
    DSU(int n): p(n + 1), siz(n + 1, 1) {
        std::iota(p.begin(), p.end(), 0);
    }
    int find(int x) {
        if(x == p[x]) return p[x];
        return p[x] = find(p[x]);
    }
    void unite(int a, int b) {
        int pa = find(a), pb = find(b);
        if(pa == pb) return;
        if(siz[pa] < siz[pb]) std::swap(pa, pb);
        p[pb] = pa;
        siz[pa] += siz[pb];
    }
    bool same(int u, int v) {return find(u) == find(v);}
    int size(int u) {return siz[find(u)];}
};
struct Edge {
    int u, v, w;
};
void solve() {
    int n; std::cin >> n;
    DSU dsu(n);
    for(int i = 1; i < n; i++) {
        int u, v, w; std::cin >> u >> v >> w;
        if(w == 1) continue;
        dsu.unite(u, v);
    }
    int minn = 1e9;
    for(int i = 1; i <= n; i++) {
        minn = std::min(minn, dsu.size(i));
    }
    i64 ans = 2 * (minn - 1) + (n - minn);
    std::cout << ans << '\n';
}

G.Kruskal

思路

由于是完全图,因此生成树只要在完全图中找出不构成环且联通的 \(n - 1\) 条边就行。因为求最小生成树,所以考虑下面的构造:

  1. \(1\) 为根。并且预处理出 \(n\) 以内的 \(2^i\)

  2. 对于任意偶数,连接到根结点上,代价为 \(0\)

  3. 对于范围内的 \(2^i - 1\) 号节点,连接到 \(2^i\) 号节点上,代价也为 \(0\)

  4. 对于范围内任意奇数节点,一定存在一个偶数节点,使得两者连接代价为 \(0\)

  5. 此时只剩下两种情况,第一张情况是已经构成了生成树,总代价为 \(0\) ;第二种情况是 \(n\) 号节点刚好是一个 \(2^i - 1\) 形式,由于没有大于它的 \(2^i\) 节点和它匹配,因此它只能连接到根上,代价为 \(1\)

因此,本题的答案只会是 \(0\) 或者 \(1\)

代码

int p[32]; //预处理的 2^i - 1
void solve() {
    int n; std::cin >> n;
    int cnt = 0;
    for(int i = 2; i <= 30; i++) {
        if(n == p[i]) cnt++;
    }
    std::cout << cnt << '\n';
}

I.双指针

思路

将式子变形:

\[a_i \times a_j = b_i \times b_j \Rightarrow \frac{a_i}{b_i} = \frac{a_j}{b_j} \]

然后直接找就好了。需要注意精度。

代码

    public static void main(String[] args) throws Exception {
        int t = read();
        while (t-- > 0) {
            int n = read();
            //int n = (int) (Math.random() * 20);
            int[] a = new int[n];
            int[] b = new int[n];
            TreeMap<Double, Long> d1 = new TreeMap<>();
            TreeMap<Double, Long> d2 = new TreeMap<>();
            for (int i = 0; i < n; i++) {
                a[i] = read();
                //a[i] = (int) (Math.random() * 20);
            }
            for (int i = 0; i < n; i++) {
                b[i] = read();
                //b[i] = (int) (Math.random() * 20);
                d1.put((double) a[i] / b[i], d1.getOrDefault((double) a[i] / b[i], 0L) + 1);
                d2.put((double) b[i] / a[i], d2.getOrDefault((double) b[i] / a[i], 0L) + 1);
            }
            long ans = 0;
            HashSet<Double> s = new HashSet<>();
            for (double val : d1.keySet()) {
                if (val > 1) {
                    continue;
                }
                if (d2.containsKey(val)) {
                    if (val == 1.0) {
                        ans += d1.get(val) * (d1.get(val) - 1) / 2;
                    } else {
                        ans += d1.get(val) * d2.get(val);
                    }
                    //pw.println(val + " " + d1.get(val) + " " + d1.get(val));
                    s.add(val);
                }
            }
            pw.println(ans);
        }
        pw.flush();
    }}
        pw.flush();
    }

J.树上DP

思路

考虑节点对答案的贡献

包含该点的子树越多,贡献越大

dfs处理所有节点的深度,越深的节点分配越高的权值,一个节点的贡献为 深度 * 权值

代码

public static void main(String[] args) throws Exception {
    int t = read();
    while (t-- > 0) {
        int n = read();
        map.clear();
        for (int i = 0; i <= n; i++) {
            map.add(new ArrayList<>());
        }
        h = new int[n];
        for (int i = 1; i < n; i++) {
            int u = read();
            int v = read();
            map.get(u).add(v);
            map.get(v).add(u);
        }
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = read();
        }
        dfs(1, -1, 1);
        Arrays.sort(a);
        Arrays.sort(h);
        long ans = 0;
        for (int i = n - 1; i >= 0; i--) {
            ans += (long) a[i] * h[i];
        }
        pw.println(ans);
    }
    pw.flush();
}
public static ArrayList<ArrayList<Integer>> map = new ArrayList<>();
public static int[] h;
public static void dfs (int root, int father, int hei) {
    h[root - 1] = hei;
    for (int node : map.get(root)) {
        if (node == father) {
            continue;
        }
        dfs(node, root, hei + 1);
    }
}

L.异或与

思路

打表观察规律

发现每一个数最多只有一个数与其对应

\(9\) 的二进制为 \(1001\)\(27\) 的二进制为 \(11011\),相与等于 \(1001\),异或等于 \(10010\)

注意到首先两个数末位异或必定等于 \(0\)

设当前数二进制为 \(a\) ,与其对应的数二进制为 \(b\)\(b\) 的第 \(1\) 位已知

\(b[i + 1] = ( b[i - 1] \ \& \ a[i - 1] ) \oplus a[i + 1]\)

判断 \(b\) 是否存在于数组即可

代码

int n = Integer.parseInt(br.readLine());
long[] arr = new long[n];
HashMap<Long, Integer> d = new HashMap<>();
String[] input = br.readLine().split(" ");
for (int i = 0; i < n; i++) {
    arr[i] = Long.parseLong(input[i]);
    d.put(arr[i], i);
}
int flag = 0;
for (int i = 0; i < n; i++) {
    long num = arr[i];
    long last = num % 2;
    num /= 2;
    long nnum = last == 1 ? 1 : 0;
    long ncur = last == 1 ? 1 : 0;
    int cnt = 1;
    while (num != 0 || ncur != 0) {
        long cur = num % 2;
        num /= 2;
        ncur = (last & ncur) ^ cur;
        nnum += ncur * (long) Math.pow(2, cnt);
        last = cur;
        cnt++;

    }
    if (d.containsKey(nnum)) {
        if (d.get(nnum) != i) {
            flag = 1;
            pw.println((i + 1) + " " + (d.get(nnum) + 1));
            break;
        }
    }
}
if (flag == 0) {
    pw.println(-1);
}
posted @ 2025-09-02 18:28  dbywsc  阅读(15)  评论(0)    收藏  举报