[PKU][POJ][1872][A Dicey Problem]

—title: [PKU][1872][A Dicey Problem]
—-link: http://acm.pku.edu.cn/JudgeOnline/problem?id=1872
—-date: 2009-07-31
-problem: 给你一个骰子, 一张r*c的方格图, 初始时骰子在位置行sr, 列sc处, 方格里有数字[-1, 6], 骰子可以上下左右滚动前进, 若目标格的数字与当前骰子顶部数字相同, 或者目标格数字为-1, 就可以将骰子滚过去, 若为0表示不可达, 求一条回路使骰子从起点出发滚回起点(保证若有回路则只有一条), 输出时有些恶心的要求, 详见原题, 若没有此回路, 输出"No Solution Possible".
solution: 模拟, DFS.
—-code: http://docs.google.com/View?id=dgtsspfh_9c8dpdxhf

没什么特别的算法, 主要是怎么表示骰子的状态. 建图的时候下标从1开始, 周围留出一圈0免去判断边界.

记录骰子的顶部数字top和朝向地图下方的数字face即可, 打个表记录特定top和face的组合时朝向地图左边的数字, 这样这三个面对面的数字只要用7减一下就可以算出来.

另外注意骰子可能多次经过同一格, 但是经过时top和face的值不同, 所以记录状态的数组要开4维, 分别是行, 列, top和face, 然后再开一个四维数组记录前趋节点, DFS的时候注意起点的前趋特殊处理一下(起始时和终止时的起点前趋都要特殊处理), 然后输出的时候注意正好9步的时候行尾不要加逗号.

[PKU][POJ][1873][The Fortified Forest]

—title: [PKU][1873][The Fortified Forest]
—-link: http://acm.pku.edu.cn/JudgeOnline/problem?id=1873
—-date: 2009-07-31
-problem: 有n棵长在整数坐标上的树, 每棵树有价值v, 木材量l, 现在要造篱笆把这些数围起来, 单位长度消耗木材量1, 忽略树的直径, 树要么整棵砍, 要么不砍, 问要造这种篱笆最少需要消耗多少价值(砍掉的树的v的和), 输出剩余的木材量和砍掉哪些树.
solution: 凸包+剪枝.
—-code: http://docs.google.com/View?id=dgtsspfh_8g2x8vhd2

跟2009-07-28的暑期校赛第7场的题目几乎一样, 输出改变了.

基础的凸包+剪枝, 每次枚举砍掉的树的数目. ("这里有一个优化是从n-1开始枚举, 到1结束, 遇到哪一次围不住了就break, 因为用i棵数围不住, i-1棵树肯定也围不住." 这个是校赛时的剪枝, 因为那时不要求砍掉树最少)

然后每次枚举都要取到砍i棵数的所有情况, 做凸包, 计算凸包周长, 跟砍掉的木材量比较. 这里的剪枝就是记录当前消耗的最优值ansv, 初始时ansv等于无穷大, 然后在DFS砍i棵树的所有情况时每深入一层都判断当前的v是否大于ansv, 如果大于就不再深入.

[PKU][POJ][1874][Trade on Verweggistan]

—title: [PKU][1874][Trade on Verweggistan]
—-link: http://acm.pku.edu.cn/JudgeOnline/problem?id=1874
—-date: 2009-07-31
-problem: 有个商人在w个地点买东西, 每个地点有堆在一起的b[i]件货物, 每件货物有价格c[j], 必须要把堆在上面的货物都买了才能买下面的货物, 商人收购的每件货物能以10元倒卖出去, 求最大利润, 以及需要买多少件货物才能达到最大利润, 若可以有多种方案, 则按货物由少到多输出前10种.
solution: 每个地点求一次最大利润再做一次0-1背包.
—-code: http://docs.google.com/View?id=dgtsspfh_7ctwkbpft

预处理一下, 设货物原价a, 那处理后就是10-a. 一开始以为要对这个数组求最大子段和, 其实只要线性扫过去就行了, 因为要取后面的货物, 前面的必须都取, 得到每个地点的最大利润后再扫一遍找出获得最大利润需要买的货物数, 同时再根据上一个地点的背包结果做一次0-1背包, 用滚动数组, 初始时bag[pre][0]=true, 其它都是false, 每次把bag[now][]清空. 最后把bag[pre][]里从小到大输出前10个值为true的即可.

注意: 如果一个地方怎么取都亏本, 就不取, 如果正好不亏本(即收益0), 也可能有多种取法, 要全部考虑进去, 考虑到某地可能一个都不取, 所以把累计收益用的数组下标从1开始, 下标0处表示一个都不取, 置0.

[PKU][POJ][3662][Telephone Lines]

—title: [PKU][3662][Telephone Lines]
—-link: http://acm.pku.edu.cn/JudgeOnline/problem?id=3662
—-date: 2009-07-31
-problem: 一个无向图有n个点, p条边, 每边有花费w[i], 可以任意指定有k条边免费, 现要求选择若干条边构成顶点1到顶点n的一条路径, 使得路径上边权最大值(不包括免费边)最小.
solution: 二分答案, 重新建图, Dijkstra

来自http://www.ruvtex.cn/wiki/USACOMonthly/2008_1_S/Telephone_Lines/题解:

我们暂且不考虑最优解的问题, 假设A为一个可行解. 可以知道如果A成立, 则B(B>=A)一定成立. 这说明解具有单调性, 即所有可行解是一个单调序列. 我们模仿二分查找的方法, 对答案进行二分尝试, 对最优解逐步逼近.

对于这道题, 假设A=4这个解成立, 则说明顶点1到N之间必存在某路径, 满足这条路径中长度大于4的边不超过K条(如果超过K条, 4就不是去除K条以外的最大值了, 与题意矛盾).

  1. 1. 判断给定的A是否为可行解, 即求从顶点1到顶点N的某条路径上, 长度大于A的边最少有多少条. 如果最少的条数小于等于K, 则A为一个可行解.
  2. 对于求最少的条数, 可以在原图的基础上构造一个新图, 改变边权. 如果一条边原来的权值大于A, 则在新图中将其权值设为1;如果一条边原来的权值小于等于A, 则在新图中将其权值设为0.
  3. 然后求出从顶点1到N的最短路径长度D, D即为从顶点1到顶点N的某条路径上, 长度大于A的边最少有多少条. 如果D<=K, 则A为可行解.

另外这题可以先做个BFS判断连通性, 若1和n之间没有路径则不用再二分.

这题WA了几次, 主要错在这两个地方:

  1. 二分时取等号时是可行解, 可行解必须包含在[beg, end]中, 注意这里右边也是闭的.
  2. 注意在没有open表来判断节点是否已在队列中的情况下, BFS的队列不能开成节点数.

代码见这里.

[PKU][POJ][1056][IMMEDIATE DECODABILITY]

—title: [PKU][1056][IMMEDIATE DECODABILITY]
—-link: http://acm.pku.edu.cn/JudgeOnline/problem?id=1056
—-date: 2009-07-30
-problem: 输入若干组二进制串, 每组结尾用一个’9′表示一组结束, 每组最多8个串, 每个串最长10个字符, 判断每组的字符串是否能组成前缀码.
solution: 二叉树

开一个1024长度的数组, 建一个静态二叉树, 用连着左孩子的边表示0, 连着右孩子的边表示1. 递归的按输入的字符串深入二叉树, 遇到0就进左子树, 反之反之. 初始时把标记数组置0, 每一步都将当前节点标记为1, 遇到字符串结尾就把节点标记为2, 这样如果遇到字符串结尾了, 而标记数组当前位置不是0, 或者没有遇到字符串结尾, 而标记数组当前位置是2, 则说明不是前缀码.

代码见这里.

{PKU}{3522}{Slim Span}

@problem: 一个无向图, 求生成树, 最小化该树权值最大的边与权值最小的边之差.
Continue Reading »

PKU(POJ) 3207 Ikki’s Story IV – Panda’s Trick

2009-07-28

题目原来的叙述不太好理解, 换个说法. 一个圆盘的边沿上有n个点, 下标从0开始, 有m条线连接2m个互不相同的点, 线可以从圆盘任意一面过, 要求任意两条线不能相交. 给出m条线(正反面随意), 问是否存在一种给每条线的正反面的分配方法, 使满足要求.

这题是2-SAT(2判定性问题)的基础题, 关键在于建图.

令第i条线(i从0开始)在正面为第2i个顶点, 在反面为第2i+1个顶点. 若第i条线和第j条线构成一个四边形的两条对角线, 则在2i和2j+1之间连一条双向边, 2i+1和2j之间连一条双向边. 这样建图后求强连通分量, 看点2i和点2i+1是否在同一个强连通分量中, 只要有, 就说明不满足要求, 如果没有则满足.

这里对建图的方式做一点解释. 首先, 我们知道若第i条线和第j条线构成一个四边形的两条对角线, 则2i和2j不相容, 又2i和2i+1中必须出现一个, 且2j和2j+1中必须出现一个, 所以可以说2i+1和2j+1中至少出现一个, 这就是2-SAT的合取式中的"或"的来路. 换句话说, 从2i到2j+1连一条有向边表示如果选择了2i就必须选择2j+1, 从2j到2i+1连一条有向边表示如果选择了2j就必须选择2i+1, 同理对于2i+1和2j+1的不相容也可以连两条有向边, 所以最后跟上面的组成了两条双向边.

关于2-SAT问题的解法, 赵爽的论文(2-SAT 解法浅析)里说得很详细.

代码见这里.

PKU(POJ) 3249 Test for Job

2009-07-27

一个有n个点, m条边的向无环图, 无重边, 每个点有值v, 范围[-20000, 20000], 从任意一个入度为0的点出发, 到任意一个出度为0的点结束, 问途中经过的点的v的和的最大值是多少.

这题初看以为就是求最长路, 故对每个入度为0的点都用了一次SPFA, 超时.其实这里SPFA本身并不耗时, 因为无环, SPFA中每个顶点只会入队一次, 但是入度为0的点多了, 速度就会下来.

其实这题DP的意图还是比较明显的, 因为无环, 以顶点v为路径结尾的最优值只依赖于v的前趋, 这个单向依赖性就是在告诉你需要DP了, 考虑逆向DP(更新后继), 只要先对顶点做一次拓扑排序即可. 注意DP可以在拓扑排序中做, 没必要分开.

注意本题路径的结尾一定是出度为0的顶点, 如果在DP过程中更新结果res, 则需要注意边数m=0的情况, 需要再DP初始化的时候就将入度和出度都为0的点与res取max.

本题吃了一个亏, 注意以后不要把赋值语句写在"[]"里面, 也不要写在if里.

代码见Google Docs.

[PKU][POJ][3592][Instantaneous Transference]

—title: [PKU][3592][Instantaneous Transference]
—-link: http://acm.pku.edu.cn/JudgeOnline/problem?id=3592
—-date: 2009-07-28
-problem: 输入一个n*m矩阵, 每格有三种字符, 字符’0′~’9′表示此处有多少矿, 到达即能采光, ‘*’表示传送点, 到达此处能传送到特定位置, 也可以选择不传送, ‘#’表示障碍物. 地图输入结束后按传送点逐行扫描的顺序给出每个传送点传送的目标位置. 采矿车从左上角出发, 只能往右或往下走, 问最多能采多少矿.
solution: 强连通分量,缩点,最长路.
—-link: http://docs.google.com/View?id=dgtsspfh_11db2d5bds

这题关键是建图, 每个格子都表示成一个顶点, 顶点之间可达就加一条有向边. 没有传送点的话这个图其实就相当于一棵树.

先求强连通分量, 然后缩点(注意把同属一个强连通分量的顶点的矿缩进一个强连通分量里), 原图变成一个有向无环图, 问题转变为从左上角顶点所在的缩点开始找一条最长路.

最长路有多种求法, 可以用Bellman-Frod, 想快一点的话用SPFA, 但是用这两个方法时注意初始化的时候要把dis设为负值, 不能设为0(因为有矿为0的点). 也可以做拓扑排序, 排序的时候沿着边的方向DP. 更简单一点直接DFS, 几行代码就搞定, 这题数据量小, 四种方法都是可以的.

注意这题所有求解图论的算法所用的数组长度别开错了, 是N*M, 而不是N.

Ecust ACM 2009 Summer Practice Contest 3

昨天的校赛时彻底脑残, 只在前44分钟做了三题大水, 后面再也没A过题, 更要命的是我真的不会做, 那道最大m子段和我就是不会, 而对模板的依赖也使得我的B在编译错误之后变得无从下手, G完全没想法, H也是一样, 最要命的就是E(求a到b之间所有数的约数的个数和), 校赛一模一样的题目啊, 那天晚上我还画了图给同学解释为什么可以将连续函数y=1/x用在这题, 今天看到这题竟然完全没印象了, 一直到结束之后队友在我的草稿纸上又把那幅图画出来的时候我才意识到自己的愚蠢. 还是题目做的太少, 不能再这样下去了.

关于最大M子段和的状态压缩, 做这题的时候我已经没想法了, 很无耻地跑到google搜索了一番, 结果搜到三篇代码竟然都是错的, 崩溃了我, 比赛以后静下心来想想, 其实是很简单的DP, 不论压缩哪一维的状态都是可以过的, 最终经过几番优化, 间复杂度变为O(nm), 空间复杂度变为O(m).

[Ecust ACM 2009 Summer Practice Contest 3][D][Max Partial Sums]

2009-07-22

最大M子段和: 给出一个整数序列, 包含负数, 求M个不相交子段, 使它们中的元素和最大, 每段长度至少为1.

很容易想到O(m*n^2)的算法, 状态转移如下:

f[m][n]表示m个子段正好覆盖到n个元素.
f[m][n] = max{f[m][n-1], f[m-1][t]} + a[n]

注意到只依赖m-1, 所以可以把第一维压缩, 又f[m-1][t]需要前一轮状态的最大值, 所以需要长度n的数组来记录, 又f[m][n]另外只依赖n-1, 所以在使用后就就无用了, 利用它来存储最大值.

f[n][1] = max{f[n-1][0], f[n-1][1]} + a[i]
f[n][0] = max{f[n-1][0], f[n][1]}

1表示当前正好到n, 0表示前一轮在n及其之前的最大值, 迭代的时候需要开临时变量.

代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 1000000, M = 30;

int _dp[N][2], _a[N];

int max(int a, int b) { return a > b ? a : b; }

int DP(int n, int m) {
    memset(_dp, 0, sizeof(_dp));
    int i, j;
    _dp[0][1] = _dp[0][0] = _a[0];
    for (i = 1; i < n; ++i) {
        _dp[i][1] = max(0, _dp[i - 1][1]) + _a[i];
        _dp[i][0] = max(_dp[i - 1][0], _dp[i][1]);
    }
    for (i = 1; i < m; ++i) {
        int pmx = _dp[i][0];
        _dp[i][1] = _dp[i - 1][1] + _a[i];
        _dp[i][0] = _dp[i][1];
        for (j = i + 1; j < n; ++j) {
            pmx = _dp[j][0];
            _dp[j][1] = max(pmx, _dp[j - 1][1]) + _a[j];
            _dp[j][0] = max(_dp[j - 1][0], _dp[j][1]);
        }
    }
    int res = _dp[m - 1][1];
    for (i = m; i < n; ++i) {
        res = max(res, _dp[i][1]);
    }
    return res;
}

int main() {
    int n, m, i;
    while (EOF != scanf("%d%d", &m, &n)) {
        for (i = 0; i < n; ++i) {
            scanf("%d", _a + i);
        }
        printf("%d\n", DP(n, m));
    }
    return 0;
}

稍微修改一下, 就可以压缩成一维数组:

#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 1000000, M = 30;

int _dp[N], _a[N];

int max(int a, int b) { return a > b ? a : b; }

int DP(int n, int m) {
    memset(_dp, 0, sizeof(_dp));
    int i, j;
    _dp[0] = _a[0];
    for (i = 1; i < n; ++i) {
        _dp[i] = max(0, _dp[i - 1]) + _a[i];
    }
    for (i = 1; i < n; ++i) {
        _dp[i] = max(_dp[i - 1], _dp[i]);
    }
    for (i = 1; i < m; ++i) {
        int pmx = _dp[i];
        _dp[i] = _dp[i - 1] + _a[i];
        for (j = i + 1; j < n; ++j) {
            int tpmx = _dp[j];
            _dp[j] = max(pmx, _dp[j - 1]) + _a[j];
            pmx = tpmx;
        }
        for (j = i + 1; j < n; ++j) {
            _dp[j] = max(_dp[j - 1], _dp[j]);
        }
    }
    return _dp[n - 1];
}

int main() {
    int n, m, i;
    while (EOF != scanf("%d%d", &m, &n)) {
        for (i = 0; i < n; ++i) {
            scanf("%d", _a + i);
        }
        printf("%d\n", DP(n, m));
    }
    return 0;
}

再又注意到m<n, 可以将第二维压缩:

f[m][i][j], i=0,1,表示滚动状态, j=0表示正好到m, j=1表示m及其之前的最大值.

f[m][1 - k][0] = max(f[m][k][0], f[m - 1][k][1]) + a[i];

f[m][1 - k][1] = max(f[m][k][1], f[m][1 - k][0]);

注意初始状态置负无穷, f[0][0][0] = f[0][0][1] = a[0];

最终代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 1000000, M = 30, INF = 1147483647;

int _a[N];

typedef int T;
T _dp[M][2][2];
T MMaxSum(int n, int m, T a[]) {
    // cannot be this:
    // fill_n(_dp[0][0], sizeof(_dp), -INF);
    fill_n(_dp[0][0], M * 2 * 2, -INF);
    int i, j, k, jend;
    _dp[0][0][0] = _dp[0][0][1] = a[0];
    for (i = 1, k = 0; i < n; ++i, k = 1 - k) {
        _dp[0][1 - k][0] = max(0, _dp[0][k][0]) + a[i];
        _dp[0][1 - k][1] = max(_dp[0][k][1], _dp[0][1 - k][0]);
        for (j = 1, jend = min(i + 1, m); j < jend; ++j) {
            _dp[j][1 - k][0] = max(_dp[j][k][0], _dp[j - 1][k][1]) + a[i];
            _dp[j][1 - k][1] = max(_dp[j][k][1], _dp[j][1 - k][0]);
        }
    }
    return _dp[m - 1][k][1];
}

int main() {
    int n, m, i;
    while (EOF != scanf("%d%d", &m, &n)) {
        for (i = 0; i < n; ++i) {
            scanf("%d", _a + i);
        }
        printf("%d\n", MMaxSum(n, m, _a));
    }
    return 0;
}

 

[Ecust ACM 2009 Summer Practice Contest 3][E][Divistors]

2009-07-22

求a到b之间(inclusive)所有数字约数的总数(总数目).

画出y=n/x的曲线图, 容易发现y=x这条线两边被曲线圈住的点是对称的, 所以只要求出上限为sqrt(n)的约数数目, 乘2, 再减去中间重复的正方形那块中的整点数目(即(floor(sqrt(n)))^2).

注意若n太大不则能用i^2<=n来做循环条件, 除非用64位, 否则会溢出. 还有记得a-1.

这题是上次去东华之前校赛的最后一题, 竟然完全忘了.

#include <cmath>
#include <cstdio>

typedef __int64 I;

I Sol(int n) {
    int i, sq = int(sqrt(double(n)));
    I res = 0;
    for (i = 1; i <= sq; ++i) {
        res += n / i;
    }
    res += res - sq * sq;
    return res;
}

int main() {
    int a, b;
    while (scanf("%d%d", &a, &b), a) {

        // note "a - 1"
        printf("%I64d\n", Sol(b) - Sol(a - 1));
    }
    return 0;
}
Page 13 of 15« First...{10}{11}{12}{13}{14}{15}