Diary_2.22

1. Is It A Tree?-树,并查集

  • 题意

    给定多条有向边的图,问这个图是不是一个树

  • 思路

    有两种思路

    • 一种是用邻接表构建这个图,然后以入度为 0 的点为根,遍历这个图

      如果入度为 0 的点有多个,或者在遍历的时候访问到了已经访问过的点,则不是一棵树

    • 另一种思路就是用并查集

      如果合并的时候两个点已经在同一个集合中了,或者最终根节点的个数多于 1 ,则不是一棵树

    // 思路1
    int cnt = 1;
    bool stop;
    map<int, vector<int> > tree;
    map<int, int> indgree;
    void solve(){
        int u, v;
        map<int, vector<int> > ctree;
        map<int, int> cindgree;
        swap(tree, ctree), swap(indgree, cindgree);
        int ccc = 0;
        while(cin >> u >> v){
            if(u==0 && v==0)    break;
            if(u==-1 && v==-1){
                stop = true;
                return;
            }
            ccc++;
            tree[u].push_back(v);
            indgree[u] = indgree[u];
            indgree[v]++;
        }
        if(ccc == 0){
            cout << "Case " << cnt++ << " is a tree." << endl;
            return;
        }
        int cc = 0, sta = -1;
        for(map<int, int>::iterator x=indgree.begin(); x!=indgree.end(); x++){
            if(x->second == 0){
                cc++;
                sta = x->first;
            }
        }
        if(sta == -1 || cc > 1){
            cout << "Case " << cnt++ << " is not a tree." << endl;
            return;
        }
        map<int, bool> vis;
        queue<int> q;
        q.push(sta);
        while(!q.empty()){
            int now = q.front();    q.pop();
            if(vis[now]){
                cout << "Case " << cnt++ << " is not a tree." << endl;
                return;
            }
            vis[now] = true;
            vector<int> c;
            swap(c, tree[now]);
            int len = c.size();
            for(int i=0; i<len; i++){
                int next = c[i];
                if(vis[next]){
                    cout << "Case " << cnt++ << " is not a tree." << endl;
                    return;
                }
                q.push(next);
            }
        }
        cout << "Case " << cnt++ << " is a tree." << endl;
    }
    signed main(){
        io;
        Test;
        int t;  t = 1;
        while(t){
            solve();
            if(stop)    break;
        }
    }
    
    // 思路2, 这个算法的所用的时间明显少于思路1
    int cnt = 1;
    bool stop;
    map<int, int> pre;
    int find(int now){
        if(pre[now] == now) return now;
        return pre[now] = find(pre[now]);
    }
    void solve(){
        int u, v, t = true;;
        map<int, int> cpre; swap(cpre, pre);
        int cc = 0;
        while(cin >> u >> v){
            if(u==0 && v==0)    break;
            if(u==-1 && v==-1){
                stop = true;
                return;
            }
            cc++;
            if(t == false)  continue;
            if(pre[u] == 0) pre[u] = u;
            if(pre[v] == 0) pre[v] = v;
            int ru = find(u), rv = find(v);
            if(ru == rv){
                t = false;
                continue;
            }
            pre[ru] = rv;
        }
        map<int, int>::iterator pos;
        map<int, bool> vis;
        for(pos=pre.begin(); pos!=pre.end(); pos++){
            vis[find(pos->first)] = true;				// 这里是并查集中判断是否在一个集合中的公式,很多时候都会用到
        }
        int len = vis.size();
        if(len > 1){
            t = false;
        }
        if(cc == 0) t = true;
        if(t)   cout << "Case " << cnt++ << " is a tree.";
        else    cout << "Case " << cnt++ << " is not a tree.";
        cout << endl;
    }
    signed main(){
        io;
        Test;
        int t;  t = 1;
        // cin >> t;
        while(t){
            solve();
            if(stop)    break;
        }
    }
    
  • 总结

    题目不难,就是简单的并查集

2. Supermarket-并查集,规划任务

  • 题意

    给定 n 个产品,每个产品都有一个价值和一个截止日期,在这个截止日期之前将这个产品做出来就能获得它的价值

    问最多能获取多少价值

  • 思路

    这道题第一眼看上去是动态规划,因为现在的能获得的最大价值其实可以由之前的时间的价值再枚举现在能完成的产品的价值来得到

    但是这样一下看上去它的时间复杂度是 o(n^2) ,但是实际做的时候就会发现第一层循环枚举时间,第二层循环枚举当前要做的产品,然后还要再枚举一层之前的时间,所以实际上需要 o(n^3) 的时间复杂度,所以舍弃这个方法

    找不到简便方法就换朴素方法,首先价值越大的产品一定首先制作,然后越接近其截止日期做越好,因为可以给其他的产品留足时间

    所以按照产品的价值排序,对于每个产品,都找其截至日期之前的一个空余的时间制作

    所以这个方法的时间复杂度也是 o(n^2) 级别的

    接下来就是一个很重要的优化,用并查集合并每个产品截至日期之前的空余的时间点

    就是 pre[i] 为第 i 天之前的第一个空余的时间,pre[i] == i 就是截至日期 i 当天就是一个空余的时间,另外,在制作完这个产品之后,第 i 天就会被占用,那就可以将 i 的根节点的根节点换成 i-1 的根节点,这样就直接找到了下一个空余的时间

    如果 pre[i] == 0 那说明所有可用的时间都用完了,那这个产品就做不了了
    实际上也是动态规划也是可以为 o(n^2) 的,但是那是在通过将时间和任务的索引合并的优化之后才能达到的

const int maxn = 1e4+10;
int n, pre[maxn];
struct node{
    int profit, deadline;
    bool operator<(const node &x)const{
        if(profit == x.profit)  return deadline > x.deadline;
        return profit > x.profit;
    }
};
node a[maxn];
void init(int len){
    for(int i=0; i<=len; i++) pre[i] = i;
}
int find(int now){
    if(now == pre[now]) return now;
    return pre[now] = find(pre[now]);
}
void solve(){
    int len = 0;
    for(int i=0; i<n; i++){
        scanf("%d %d", &a[i].profit, &a[i].deadline);	// 用scanf和printf的时间比关流后的cin和cout都快的多
        len = max(len, a[i].deadline);
    }
    init(len);
    sort(a, a+n);
    int ans = 0;
    for(int i=0; i<n; i++){
        int u = a[i].deadline;
        int ru = find(u);
        if(ru > 0){
            ans += a[i].profit;
            pre[ru] = ru-1;								// 合并
        }
    }
    printf("%d\n", ans);
}
signed main(){
    // io;
    Test;
    int t;  t = 1;
    // cin >> t;
    while(scanf("%d", &n) != EOF){
        solve();
    }
}
  • 总结

    首先想到动态规划,但是动态规划的时间复杂度由于可以处理可变时间的问题和朴素算法差距不大,所以用并查集进行压缩路径的优化时间复杂度更优

3. Make it Simple-图论

  • 题意

    给定一个图,要求计算需要删除多少给自环和多重边使得其是一个简单图

  • 思路

    直接检查两个点的关系

void solve(){
    int n, m;   cin >> n >> m;
    map<int, map<int, bool>> tree;
    int ans = 0;
    for(int i=0; i<m; i++){
        int u, v;   cin >> u >> v;
        if(u == v)  ans++;
        else{
            if(tree[u][v])  ans++;
            else{
                tree[u][v] = true;
                tree[v][u] = true;
            }
        }
    }
    cout << ans << endl;
}
  • 总结

    这道题很简单,但是需要注意,有时候邻接表可以用 map 存,这样的好处是可以很轻松的查询两个点之间存在的关系

4. Swap to Gather-构造

#define int long long
void solve(){
    int len;    cin >> len;
    string s;   cin >> s;
    int cnt = 0;
    vector<int> pos;
    for(int i=0; i<len; i++){
        if(s[i] == '1'){
            cnt++;
            pos.push_back(i);
        }
    }
    int ans = 0, cntl = 0, cntr = 0;
    for(int i=0; i<cnt; i++){
        ans += abs(pos[i]-i);
        if(pos[i]<i)   cntl++;
    }
    cntr = cnt-cntl;
    // cout << cntl << ' ' << cntr << endl;
    for(int i=1; i<len; i++){
        for(int k=cntl; k<cnt; k++){
            if(pos[k] < i+k)   cntl++, cntr--;
            else    break;
        }
        // cout << cntl << ' ' << cntr << endl;
        ans = min(ans, ans+cntl-cntr);
    }
    cout << ans << endl;
}

Cost of the Array-构造,贪心

void solve(){
    int n, k;   cin >> n >> k;
    int a[n];
    for(int i=0; i<n; i++){
        cin >> a[i];
    }
    if(n == k){
        for(int i=0; i<n; i++){
            if(i%2)
            if(a[i] != (i+1)/2){
                cout << (i+1)/2 << endl;
                return;
            }
        }
        cout << n/2+1 << endl;
        return;
    }
    for(int i=1; i<n-k+2; i++){
        if(a[i] == 1)   continue;
        else{
            cout << 1 << endl;
            return;
        }
    }
    cout << 2 << endl;
}
posted @ 2025-02-22 21:52  Devpp  阅读(13)  评论(0)    收藏  举报