• Loj #3118. 「IOI2017」玩具火车


    Description

    传送门


    Solution

    非常妙的一道题。

    首先题目中的判定条件“走到拥有关键点的环”是一个不好判定的条件,那么考虑将问题转化变为好判定的。

    将原来的操作“保留一条边其它边全部删除”变为“选择一条边走出去”,同时,将获胜条件从“走到拥有关键点的环”变为“可以无限次经过关键点”。

    如果能无限次走到环上,那么就相当于之前的走到有关键点的环上,除非后手选择一条边离开环,火车将一直在环上绕圈不停的走关键点,但是如果后手选择了一条离开环的边,那他在第一次经过那个点的时候就不应该选择走上环的边,但是他走上环是因为在那个点走上环最优,所以会产生矛盾,那么转化后的问题和转化前的问题就是等价的了。

    考虑“先手可以无限次走关键点”如何判定,如果先手能走无限次关键点,那么对于所有可能走到的点,先手都能迫使后手走到关键点。

    即我们求出图上先手能迫使后手走上关键点的点,如果这些点是全集,则后手在这些点必须无限次的走上关键点。

    如果这些点不是关键点,那么这些点的补集,一定是后手获胜,以为后手总可以不走到关键点。

    同时,先手能迫使后手走上关键点的点可能不能无限次的走上,当且仅当后手在这些点可以进入之前求过的后手必胜的点集。

    那么我们发现先手可能获胜的点集又缩小了,继续在这个缩小后的的点集上搜出先手可能获胜的点集,直到满足该点集就是全集的条件,那么剩下的就是先手必胜的点集。

    由于每次迭代总会使先手可能获胜的点集变小,所以最多迭代(O(n))次,每次时间复杂度(O(n + m)),总体最坏是(O(n ^ 2 + nm)),可以通过本题。


    Code

    #include "train.h"
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 5000;
    
    int vis[N + 50], res;
    
    vector<int> edge1[N + 50], edge2[N + 50];
    
    vector<int> Get(vector<int> a, vector<int> res, vector<int> r, int typ)
    {
        int n = a.size();
        memset(vis, 0, sizeof(vis));
        vector<int> ans(N + 50), deg(N + 50);
        queue<int> q;
        for (int i = 0; i < n; i++) if (r[i] && res[i]) q.push(i), ans[i] = 1;
        for (int i = 0; i < n; i++) for (int j = 0; j < edge1[i].size(); j++) if (res[edge1[i][j]]) if (typ ^ a[i]) deg[i]++; else deg[i] = 1;
        while (!q.empty())
        {
            int u = q.front(); q.pop();
            for (vector<int>::iterator it = edge2[u].begin(); it != edge2[u].end(); it++)
            {   
                int v = *it;
                if (!ans[v] && res[v])
                {
                    deg[v]--;
                    if (!deg[v]) ans[v] = 1, q.push(v);
                }
            }
        }
        return ans;
    }
    
    vector<int> who_wins(vector<int>a, vector<int>r, vector<int>u, vector<int>v)
    {
        int n = a.size(), m = u.size();
        vector<int> res(N + 50);
        for (int i = 0; i < n; i++) res[i] = 1;
        for (int i = 0; i < m; i++) edge1[u[i]].push_back(v[i]), edge2[v[i]].push_back(u[i]);
        while (1)
        {
            int flag = 1;
            vector<int> fa = Get(a, res, r, 1);
            for (int i = 0; i < n; i++)
                if (!fa[i] && res[i])
                {
                    flag = 0;
                    break;
                }
            if (flag) return res;
            for (int i = 0; i < n; i++) fa[i] ^= 1;
            vector<int> fb = Get(a, res, fa, 0);
            for (int i = 0; i < n; i++) if (fb[i]) res[i] = 0;
        }
        return res;
    }
    
  • 相关阅读:
    Android Touch事件的分发过程
    使用runOnUiThread更新UI
    Sqlite访问数据库很慢的问题
    资源收集
    mongdb shard集群均衡导致宿主机CPU飙到100%处理
    Harbor安装
    springboot 启动脚本获取pid问题
    androidstudio build 时间太长处理
    修改 Docker 的 daemon.json后启动失败
    关于在centos7 64为引用android so引发的问题修复
  • 原文地址:https://www.cnblogs.com/Tian-Xing-Sakura/p/13912216.html
Copyright © 2020-2023  润新知