作者: KSkun

[SDOI2010]地精部落 题解

[SDOI2010]地精部落 题解

题目地址:洛谷:【P2467】[SDOI2010]地精部落 – 洛谷、BZOJ:Problem 1925. — [Sdoi2010]地精部落

题目描述

传说很久以前,大地上居住着一种神秘的生物:地精。
地精喜欢住在连绵不绝的山脉中。具体地说,一座长度为N的山脉H可分为从左到右的N段,每段有一个独一无二的高度Hi,其中Hi是1到N之间的正整数。
如果一段山脉比所有与它相邻的山脉都高,则这段山脉是一个山峰。位于边缘的山脉只有一段相邻的山脉,其他都有两段(即左边和右边)。
类似地,如果一段山脉比所有它相邻的山脉都低,则这段山脉是一个山谷。
地精们有一个共同的爱好——饮酒,酒馆可以设立在山谷之中。地精的酒馆不论白天黑夜总是人声鼎沸,地精美酒的香味可以飘到方圆数里的地方。
地精还是一种非常警觉的生物,他们在每座山峰上都可以设立瞭望台,并轮流担当瞭望工作,以确保在第一时间得知外敌的入侵。
地精们希望这N段山脉每段都可以修建瞭望台或酒馆的其中之一,只有满足这个条件的整座山脉才可能有地精居住。
现在你希望知道,长度为N的可能有地精居住的山脉有多少种。两座山脉A和B不同当且仅当存在一个i,使得Ai≠Bi。由于这个数目可能很大,你只对它除以P的余数感兴趣。

输入输出格式

输入格式:
输入文件goblin.in仅含一行,两个正整数N, P。

输出格式:
输出文件goblin.out仅含一行,一个非负整数,表示你所求的答案对P取余之后的结果。

输入输出样例

输入样例#1:

4 7

输出样例#1:

3

说明

【数据规模和约定】
对于20%的数据,满足N≤10;
对于40%的数据,满足N≤18;
对于70%的数据,满足N≤550;
对于100%的数据,满足3≤N≤4200,P≤1e9。

题解

40%一看就是个状压,也是我最开始想到的解法。dp[i][j][S]表示选到第i位,最后一位是j,且选数状态为S的方案数,枚举没选的数填上向后+1转移。
N这么小是不是可以暴力打表哇(雾)。
后面这些部分分就需要一些思考了。建议在拿到这道题的时候开始手玩一下这个序列的性质。比如有这么些性质:

  1. 如果i和i+1在序列中不相邻,交换它们俩序列仍然是一个波动序列。你可以枚举i和i+1为峰谷的4种情况验证;
  2. 把序列中的每个数变为N-这个数+1,序列仍然是波动序列,只是山峰山谷反了一下。

我们需要的是一个方便统计答案的状态,状态中要包含区分不同状态的要素,比如,开头的数字?上面状压的问题在于我不知道下一个要填什么数字,因此我们自然不能让状态受到下一个位置填什么数这个问题的限制。有一种方法是缩小问题的规模,就是先缩小n的范围,解决小问题,然后再向大问题扩展,这样做可行吗?结合上面两个问号之前的猜想,我们可以设计出如下状态:假如我们已经做好了一段序列,而且这段序列用的是[1, i]这些数字,且以j开头,且规定开头是个山峰,设计出的DP状态是dp[i][j]。
现在的问题是如何从小问题向大问题转移,或者说大问题应该如何从小问题中计算答案。我们可以从性质1得知,如果j-1和j不相邻,dp[i][j]中应该包含一个dp[i][j-1],即交换一次j和j-1形成新的序列。那么另外一部分就是j和j-1相邻,此时j-1是山谷,也就是说,我们只需要求出数字在[1, i]{j}中且j-1为开头的山谷的方案数。这个排除j很烦人,但是如果考虑把这个数列中[j+1, i]的数字全部减1,对于序列的合法性是没有影响的,因此这个等价于数字在[1, i-1]中且j-1为开头的山谷的方案数。我们想到了性质2,如果把序列反一下就好了,因此就变成了(i-1)-(j-1)+1=i-j+1开头的山峰的方案数,这个就是dp[i-1][i-j+1]。结合上面的讨论,方程如下
dp[i][j] = dp[i][j-1] + dp[i-1][i-j+1]
答案就是\sum_{i=2}^N dp[N][i]。复杂度O(n^2),空间可以滚动数组搞成O(n)的。

代码

// Code by KSkun, 2018/4
#include <cstdio>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 4205;

int n, p;
LL dp[2][MAXN];

int main() {
    n = readint(); p = readint();
    dp[0][2] = 1;
    for(int i = 3; i <= n; i++) {
        for(int j = 2; j <= i; j++) {
            dp[i & 1][j] = (dp[i & 1][j - 1] + dp[i & 1 ^ 1][i - j + 1]) % p;
        }
    }
    LL ans = 0;
    for(int i = 2; i <= n; i++) {
        ans = (ans + dp[n & 1][i]) % p;
    }
    ans = ans * 2 % p;
    printf("%lld", ans);
    return 0;
}
[SDOI2009]学校食堂 题解

[SDOI2009]学校食堂 题解

题目地址:洛谷:【P2157】[SDOI2009]学校食堂 – 洛谷、BZOJ:Problem 1226. — [SDOI2009]学校食堂Dining

题目描述

小F 的学校在城市的一个偏僻角落,所有学生都只好在学校吃饭。学校有一个食堂,虽然简陋,但食堂大厨总能做出让同学们满意的菜肴。当然,不同的人口味也不一定相同,但每个人的口味都可以用一个非负整数表示。 由于人手不够,食堂每次只能为一个人做菜。做每道菜所需的时间是和前一道菜有关的,若前一道菜的对应的口味是a,这一道为b,则做这道菜所需的时间为(a or b)-(a and b),而做第一道菜是不需要计算时间的。其中,or 和and 表示整数逐位或运算及逐位与运算,C语言中对应的运算符为“|”和“&”。
学生数目相对于这个学校还是比较多的,吃饭做菜往往就会花去不少时间。因此,学校食堂偶尔会不按照大家的排队顺序做菜,以缩短总的进餐时间。
虽然同学们能够理解学校食堂的这种做法,不过每个同学还是有一定容忍度的。也就是说,队伍中的第i 个同学,最多允许紧跟他身后的Bi 个人先拿到饭菜。一旦在此之后的任意同学比当前同学先拿到饭,当前同学将会十分愤怒。因此,食堂做菜还得照顾到同学们的情绪。 现在,小F 想知道在满足所有人的容忍度这一前提下,自己的学校食堂做完这些菜最少需要多少时间。

输入输出格式

输入格式:
第一行包含一个正整数C,表示测试点的数据组数。 每组数据的第一行包含一个正整数N,表示同学数。 每组数据的第二行起共N行,每行包含两个用空格分隔的非负整数Ti和Bi,表示按队伍顺序从前往后的每个同学所需的菜的口味和这个同学的忍受度。 每组数据之间没有多余空行。

输出格式:
包含C行,每行一个整数,表示对应数据中食堂完成所有菜所需的最少时间。

输入输出样例

输入样例#1:

2
5
5 2
4 1
12 0
3 3
2 2
2
5 0
4 0

输出样例#1:

16
1

说明

对于第一组数据:
同学1允许同学2或同学3在他之前拿到菜;同学2允许同学3在他之前拿到菜;同学3比较小气,他必须比他后面的同学先拿菜……
一种最优的方案是按同学3、同学2、同学1、同学4、同学5做菜,每道菜所需的时间分别是0、8、1、6及1。
【数据规模和约定】
对于30%的数据,满足1 ≤ N ≤ 20。
对于100%的数据,满足1 ≤ N ≤ 1,000,0 ≤ Ti ≤ 1,000,0 ≤ Bi ≤ 7,1 ≤ C ≤ 5。
存在30%的数据,满足0 ≤ Bi ≤ 1。
存在65%的数据,满足0 ≤ Bi ≤ 5。
存在45%的数据,满足0 ≤ Ti ≤ 130。

题解

学会观察数据范围是一个非常有用的技巧。
我们需要一个规模大致是O(n^2)的算法。对于这题,我们实际上要找的是一个排列,使得满足每个人的限制条件且两两异或的和最小。由于限制条件的存在,我们似乎无法贪心处理,得使用DP。那DP把啥放状态里呢,分层处理n个同学是没问题的,但是怎么来处理之后的同学排到前面去这个问题呢?我们注意到Bi的范围是很小的,就可以状压了。然后再记一下最后做的是谁的菜,便于计算下一道菜的时间,这样状态就完成了。dp[i][S][j]表示前i-1个同学的菜做完了,i及i后面7位同学的情况是S,且最后一位做菜的是i+j这个状态。需要注意的是,由于j可能最小取到-8,因此需要对j+8处理成非负整数。
接着我们考虑转移问题。有个显然的情况就是当i的菜已经做了,那么我们可以把状态dp[i][S][j]推到dp[i+1][S\i][j-1]去,因为它们是等价的状态。而另外一种状态需要枚举后面这8个同学中下一个做谁的菜比较优,注意此处枚举的时候要考虑前面同学的限制,可以记录前面同学最近允许后面哪个同学比自己前,超过限制就停止枚举。我们对于每一层先枚举后8个同学的状态,再枚举上一个做菜的同学,实现层间转移和向下一层的转移。由于本题的转移情况比较复杂,这里并不能写出转移方程。
本题在状压上下了很大的功夫,只要想到状压存状态,其他的就可以暴力枚举解决。状压真是一个暴力的东西啊。

代码

// Code by KSkun, 2018/4
#include <cstdio>
#include <cstring>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 1005, INF = 0x3f3f3f3f;

int T, n, t[MAXN], b[MAXN], dp[MAXN][1 << 8][20];

int main() {
    T = readint();
    while(T--) {
        n = readint();
        for(int i = 1; i <= n; i++) {
            t[i] = readint(); b[i] = readint();
        }
        memset(dp, 0x3f, sizeof(dp));
        dp[1][0][7] = 0;
        for(int i = 1; i <= n; i++) {
            for(int j = 0; j < (1 << 8); j++) {
                for(int k = -8; k <= 7; k++) {
                    if(dp[i][j][k + 8] == INF) continue;
                    if(j & 1) {
                        dp[i + 1][j >> 1][k + 7] = std::min(dp[i + 1][j >> 1][k + 7], 
                            dp[i][j][k + 8]);
                    } else {
                        int lim = INF;
                        for(int p = 0; p <= 7; p++) {
                            if(j & (1 << p)) continue;
                            if(i + p > lim) break;
                            lim = std::min(lim, i + p + b[i + p]);
                            dp[i][j | (1 << p)][p + 8] = std::min(dp[i][j | (1 << p)][p + 8], 
                                dp[i][j][k + 8] + (i + k ? t[i + k] ^ t[i + p] : 0));
                        }
                    }
                }
            }
        }
        int ans = INF;
        for(int i = 0; i <= 8; i++) {
            ans = std::min(ans, dp[n + 1][0][i]);
        }
        printf("%d\n", ans);
    }
    return 0;
}
[TopCoder12432]CurvyonRails 题解

[TopCoder12432]CurvyonRails 题解

题目地址:TopCoder Arena:Practice – TopCoder Arena、vjudge:CurvyonRails – TopCoder 12432 – Virtual Judge

题目描述

Cat Carol is the queen of Gridland. Gridland is a rectangular country divided into a grid of unit square cells. Each cell contains either a town or a wasteland. Carol has decided to construct some railroad tracks according to the following rules:

  • The cells with wasteland must not contain any railroad tracks.
  • Each town cell has to contain a single segment of railroad tracks that connects two of its four sides.
  • Each segment of tracks has to be connected to two other segments of tracks (one on each end).

Note that Carol does not require the entire set of tracks to be connected. Configurations that consist of multiple disjoint loops are acceptable, too.
A Curvy is a curious animal indigenous to Gridland. These animals love curves and hate straight things. Some of the towns in Gridland are inhabited by Curvies. If such a town happens to contain a straight segment of tracks (i.e., a segment that connects two opposite sides of the cell), the Curvies in the town are very unhappy.
You are given a String[] field that describes Gridland: each character of each element of field describes one of its cells. The meaning of individual characters follows.

  • The character ‘.’ represents a town without Curvies.
  • The character ‘C’ represents a town with Curvies.
  • The character ‘w’ represents a wasteland.

Compute and return the minimal number of towns with unhappy Curvies when the railroad tracks are constructed according to the above constraints. If there is no way to construct the railroads according to the given rules, return -1 instead.
要求在一个方格图上修建火车轨道,使得轨道都能构成自环。注意有一些格子有障碍,还有些格子在放置直轨道的时候产生费用。

定义名

类名:CurvyonRails
函数名:getmin
参数:vector <string>
返回值:int
函数声明:int getmin(vector <string> field)

样例

样例#1:

..
..

样例#1结果:

0

其他样例参见原网站。

说明

  • field will contain between 1 and 25 elements, inclusive.
  • Each element of field will contain between 1 and 25 characters, inclusive.
  • Each element of field will contain the same number of characters.
  • Each character of each element of field will be ‘.’, ‘w’ or ‘C’.

题解

参考资料:Topcoder SRM570 900 CurvyonRails – Enigma_aw – 博客园
如果把方格图抽象成普通图,一条轨道会使一个格子产生2的度数。我们考虑对这个方格图黑白染色,以便控制度数/流量平衡的条件,即度数为2。在这里不使用拆点的方法是因为图是无向图,不需要区分出度与入度。
我们可以建立一个源→黑点→白点→汇的网络,其中,所有的边都设置成容量2的边,用于限制度数。但是这样的图显然是不能够符合要求的,因为我们还要区分直轨弯轨与费用问题。对于直轨弯轨,我们发现直轨只产生横向或纵向的2个度数,而弯轨产生的度数是横纵各1的。我们对一个点拆成横向度数和纵向度数两个点,在黑白点的连接时也区分横纵度数连边即可。我们再用一个总点来限流,即现在的网络变为了源→黑点→黑点的横/纵点→白点的横/纵点→白点→汇,其中源→黑点→黑点的横/纵点之间的边都为容量2的边(白点类似)。至于费用问题,由于我们已经区分了横纵点,对于直轨会产生费用的点,我们从总点向横/纵点建边时建一条容量1费用0的边,再建一条容量1费用1的边,就可以表示如果同一方向产生2个度数产生的费用了。
网络上跑最小费用最大流即可,如果不能满流,说明无解。

代码

由于TopCoder要求实现一个class,下面的代码并不是标准输入/输出格式(传统OI题目)的实现方式,但是把调试注释给去掉就可以用标准输入/输出了。

// Code by KSkun, 2018/4
#include <cstdio>
#include <cstring>

#include <iostream>
#include <vector>
#include <string>
#include <queue>

const int MAXN = 100005, INF = 1e9;

struct Edge {
    int to, cap, cost, nxt;
} gra[MAXN << 1];
int head[MAXN], tot;

inline void reset() {
    memset(head, -1, sizeof(head)); tot = 0;
}

inline void addedge(int u, int v, int cap, int cost) {
    gra[tot] = Edge {v, cap, cost, head[u]}; head[u] = tot++;
    gra[tot] = Edge {u, 0, -cost, head[v]}; head[v] = tot++;
}

int dis[MAXN], f[MAXN], pre[MAXN], pree[MAXN];
std::queue<int> que;
bool inque[MAXN];

inline bool spfa(int s, int t) {
    memset(dis, 0x3f, sizeof(dis));
    memset(f, 0, sizeof(f));
    que.push(s); inque[s] = true; f[s] = INF; dis[s] = 0;
    while(!que.empty()) {
        int u = que.front(); que.pop(); inque[u] = false;
        for(int i = head[u]; ~i; i = gra[i].nxt) {
            int v = gra[i].to;
            if(gra[i].cap > 0 && dis[v] > dis[u] + gra[i].cost) {
                dis[v] = dis[u] + gra[i].cost;
                f[v] = std::min(f[u], gra[i].cap);
                pre[v] = u; pree[v] = i;
                if(!inque[v]) {
                    inque[v] = true;
                    que.push(v);
                }
            }
        }
    }
    return f[t] > 0;
}

int flow, cost;

inline void mcmf(int s, int t) {
    while(spfa(s, t)) {
        flow += f[t];
        cost += f[t] * dis[t];
        for(int i = t; i != s; i = pre[i]) {
            gra[pree[i]].cap -= f[t]; gra[pree[i] ^ 1].cap += f[t];
        }
    }
}

int n, m, S, T;

class CurvyonRails {
public:
    inline int getmin(std::vector<std::string> field) {
        n = field.size(); m = field[0].size();
        S = n * m * 3 + 1; T = S + 1;
        reset();
        int sum = 0;
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                int ii = i - 1, jj = j - 1;
                if(field[ii][jj] == 'w') continue;
                if(!((i + j) & 1)) {
                    sum += 2;
                    addedge(S, (i - 1) * m + j, 2, 0);
                    if(field[ii][jj] != 'C') {
                        addedge((i - 1) * m + j, (i - 1) * m + j + n * m, 2, 0);
                        addedge((i - 1) * m + j, (i - 1) * m + j + n * m * 2, 2, 0);
                    } else {
                        addedge((i - 1) * m + j, (i - 1) * m + j + n * m, 1, 0);
                        addedge((i - 1) * m + j, (i - 1) * m + j + n * m * 2, 1, 0);
                        addedge((i - 1) * m + j, (i - 1) * m + j + n * m, 1, 1);
                        addedge((i - 1) * m + j, (i - 1) * m + j + n * m * 2, 1, 1);
                    }
                    if(j - 1 >= 1 && field[ii][jj - 1] != 'w') {
                        addedge((i - 1) * m + j + n * m, (i - 1) * m + j - 1 + n * m, 1, 0);
                    }
                    if(j + 1 <= m && field[ii][jj + 1] != 'w') {
                        addedge((i - 1) * m + j + n * m, (i - 1) * m + j + 1 + n * m, 1, 0);
                    }
                    if(i - 1 >= 1 && field[ii - 1][jj] != 'w') {
                        addedge((i - 1) * m + j + n * m * 2, 
                            (i - 2) * m + j + n * m * 2, 1, 0);
                    }
                    if(i + 1 <= n && field[ii + 1][jj] != 'w') {
                        addedge((i - 1) * m + j + n * m * 2, i * m + j + n * m * 2, 1, 0);
                    }
                } else {
                    addedge((i - 1) * m + j, T, 2, 0);
                    if(field[ii][jj] != 'C') {
                        addedge((i - 1) * m + j + n * m, (i - 1) * m + j, 2, 0);
                        addedge((i - 1) * m + j + n * m * 2, (i - 1) * m + j, 2, 0);
                    } else {
                        addedge((i - 1) * m + j + n * m, (i - 1) * m + j, 1, 0);
                        addedge((i - 1) * m + j + n * m * 2, (i - 1) * m + j, 1, 0);
                        addedge((i - 1) * m + j + n * m, (i - 1) * m + j, 1, 1);
                        addedge((i - 1) * m + j + n * m * 2, (i - 1) * m + j, 1, 1);
                    }
                }
            }
        }
        mcmf(S, T);
        if(flow < sum) {
            return -1;
        } else {
            return cost;
        }
    }
};

/*int main() {
    std::string t;
    std::vector<std::string> field;
    while(!getline(std::cin, t).eof()) {
        field.push_back(t);
    }
    CurvyonRails solver;
    printf("%d", solver.getmin(field));
    return 0;
}*/