TCO2012Round1A-3-EllysLights


题目大意:
     一系列开关对应一系列灯泡,对应关系由 String[] switches表示,switches[i][j]取值'Y'表示开关i可以控制灯泡j,取'N'表示没有控制关系。每个开关可能控制任意多个灯泡,每个灯泡最多由两个开关控制。开关的每次动作都会改变对应灯泡的状态(亮变暗或者暗变亮),现在已知所有灯泡的初始状态String initial,以及开关灯泡对应关系switches,求使得所有灯泡变暗的最少开关动作次数是多少?如果无法实现则返回-1.
     数据规模:灯泡数[1,50], 开关数[1,50]
     
思路:
     首先可以确认最优成功方案中每一个开关的动作次数至多为1,因为两次动作的效果是会互相抵消的。所以,每一个开关的最终状态只有两种情况,有动作或者没动作。由于开关数最多可以达到50,如果遍历所有可能性需要2^50,显然不靠谱。
     需要进一步分析问题,由于每个灯泡最多对应两个开关,所以考虑每个灯泡拥有不同开关数时的情况。
  • 如果没有对应任何开关,则该灯泡初始状态必须是暗的,否则返回-1;
  • 如果只有对应一个开关,则根据该灯泡的初始状态,该开关只能有一种动作;
  • 如果对应两个开关,则根据该灯泡的初始状态,这两个开关可以有两种动作选择。
     根据上面的分析可以知道,如果某个开关是一个灯泡的唯一开关,那么该开关的最终状态是确定的。即便是对于拥有两个开关的灯泡,其任意一个开关状态确定了,另一个也就确定了。所以,实际的有效状态数应给会少很多。
     从图论的角度考虑开关和开关间的关系,我们把所有的开关和灯泡视为一个图中的顶点,开关与灯泡的对应关系作为边,那么对于任意一个连通图中的开关与灯泡,所有开关的总状态数最多只有2个。理由:任意选择一个开关,假设它的一种最终状态,那么考虑所有与该开关连接的灯泡,如果这些灯泡有些还连接了其他开关,那么这些开关的最终状态就可以确定的,依次遍历下去就可以确定该连通图中的所有开关的状态。因此,在一个连通图中,只要任意设定一个开关的状态就可以唯一确定所有其他开关的状态(假设可行的话)。所以,最多只能有两种状态。
     由于不同连通图之间在开关方案上是完全独立的,所以可以分开考虑,分别取最优方案。因此,可以通过DFS遍历每一个连通图的所有的两个状态来求解。算法的复杂度为O(M*N), 其中M为开关数,N为max(开关数,灯泡数)。

后记:
     看了官方题解后才知道,这道题其实可以转化为2-satisfiability问题,详细请看官方题解。一般2-satisfiability问题需要通过有向图的强连通分割来解决,如果条件x与其互补条件x'属于同一个强连通图则无解。有向图的强连通分割可以参考算法导论22.5节,大致思路是通过两次DFS来求解,第一次计算每一个顶点dfs过程的finish time,第二次对原图的逆向图(所有边换方向)按照前一次dfs得到的finish time降序(实际上就是原图的拓扑序)进行dfs,第二次dfs得到的每一个dfs tree就是一个强连通子图。这道题所构建的2-satisfiability图比较特殊,每条边都是双向边,所以有向图的强连通图分割可以弱化为无向图的连通图分割,随便一次BFS还是DFS都可以解决,由于题目数据规模很小,使用Floyd算法都是ok的。完成连通图分解之后,在各连通图内比较节点的两种取值方案,取各自的最优解便可以形成最终所需的最优方案。实际上,以上对2-satisfiability求解的过程与本人解法的求解过程本质上是一样的,只不过在构建2-satisfiability图时直接把问题转成了开关与开关之间的关系,本人的解法在dfs中还是通过灯来传递开关的关系。这里不得不仰慕一下前辈们的牛逼之处,通过将实际问题提炼成经典算法问题的确可以帮助分析和求解问题过程中让思路更加清晰。

Java代码:
public class EllysLights {
    private String   initial  = null;
    private String[] switches = null ;
    private int[][]  lt2sw     = null;
    int              sn, ln;
    private int dfs(int v, int[] switchUsed, boolean[] lightUsed, int act) {
        if (switchUsed[v] != -1) {
            if (switchUsed[v] == act) {
                return 0;
            } else {
                return -1;
            }
        }
        switchUsed[v] = act;
        int res = act;
        for (int i = 0; i < ln; ++i) {
            if (switches [v].charAt(i) == 'Y' && !lightUsed[i]) {
                lightUsed[i] = true;
                if (lt2sw [i][1] == -1) {
                    if((initial .charAt(i) == 'N') == (act == 1)){
                        return -1;
                    }
                } else {
                    int u = lt2sw [i][0] == v ? lt2sw[i][1] : lt2sw[i][0];
                    int r = dfs(u, switchUsed, lightUsed, initial.charAt(i) == 'Y' ? 1 - act : act);
                    if (r == -1) {
                        return -1;
                    }
                    res += r;
                }
            }
        }
        return res;
    }
    public int getMinimum(String initial_, String[] switches_) {
        initial = initial_;
        switches = switches_;
        sn = switches.length ;
        ln = initial.length();
        lt2sw = new int[ln][2];
        for (int i = 0; i < ln; ++i) {
            Arrays. fill(lt2sw[i], -1);
        }
        for (int i = 0; i < switches. length; ++i) {
            for (int j = 0; j < ln; ++j) {
                if (switches [i].charAt(j) == 'Y') {
                    if (lt2sw [j][0] == -1) {
                        lt2sw[j][0] = i;
                    } else {
                        lt2sw[j][1] = i;
                    }
                }
            }
        }
        for (int i = 0; i < ln; ++i) {
            if (initial .charAt(i) == 'Y' && lt2sw[i][0] == -1) {
                return -1;
            }
        }
        int[] switchUsed = new int[ sn];
        boolean[] lightUsed = new boolean[ ln];
        Arrays. fill(switchUsed, -1);
        int res = 0;
        for (int i = 0; i < sn; ++i) {
            if (switchUsed[i] != -1) {
                continue;
            }
            int[][] tmpSwitchUsed = new int[2][ sn];
            boolean[][] tmpLightUsed1 = new boolean[2][ ln];
            int[] r = new int[2];
            for (int j = 0; j < 2; ++j) {
                System. arraycopy(switchUsed, 0, tmpSwitchUsed[j], 0, sn);
                System. arraycopy(lightUsed, 0, tmpLightUsed1[j], 0, ln);
                r[j] = dfs(i, tmpSwitchUsed[j], tmpLightUsed1[j], j);
            }
            if (r[0] == -1 && r[1] == -1) {
                return -1;
            } else if (r[0] == -1 || (r[1] != -1 && r[0] > r[1])) {
                switchUsed = tmpSwitchUsed[1];
                lightUsed = tmpLightUsed1[1];
                res += r[1];
            } else {
                switchUsed = tmpSwitchUsed[0];
                lightUsed = tmpLightUsed1[0];
                res += r[0];
            }
        }
        return res;
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值