月度归档: 2018 年 3 月

[HAOI2015]树上操作 题解

[HAOI2015]树上操作 题解

题目地址:洛谷:【P3178】[HAOI2015]树上操作 – 洛谷、BZOJ:Problem 4034. — [HAOI2015]树上操作

题目描述

有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个操作,分为三种:
操作 1 :把某个节点 x 的点权增加 a 。
操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。
操作 3 :询问某个节点 x 到根的路径中所有点的点权和。

输入输出格式

输入格式:
第一行包含两个整数 N, M 。表示点数和操作数。接下来一行 N 个整数,表示树中节点的初始权值。接下来 N-1 行每行两个正整数 from, to , 表示该树中存在一条边 (from, to) 。再接下来 M 行,每行分别表示一次操作。其中第一个数表示该操作的种类( 1-3 ) ,之后接这个操作的参数( x 或者 x a ) 。

输出格式:
对于每个询问操作,输出该询问的答案。答案之间用换行隔开。

输入输出样例

输入样例#1:

5 5
1 2 3 4 5
1 2
1 4
2 3
2 5
3 3
1 2 1
3 5
2 1 2
3 3

输出样例#1:

6
9
13

说明

对于 100% 的数据, N,M<=100000 ,且所有输入数据的绝对值都不会超过 10^6 。

题解

树剖那篇文章的例题还裸。

代码

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

#include <vector>

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;
    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 = 100005;

int n, m, w[MAXN], ut, vt, op, xt, at;
int fa[MAXN], siz[MAXN], son[MAXN], dfn[MAXN], ptn[MAXN], top[MAXN], dep[MAXN], cnt;
std::vector<int> gra[MAXN];

inline void addedge(int u, int v) {
    gra[u].push_back(v);
    gra[v].push_back(u);
}

inline void dfs1(int u) {
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(v == fa[u]) continue;
        dep[v] = dep[u] + 1;
        fa[v] = u;
        dfs1(v);
        siz[u] += siz[v] + 1;
        if(siz[v] > siz[son[u]]) son[u] = v;
    }
}

inline void dfs2(int u, int tp) {
    top[u] = tp;
    dfn[u] = ++cnt;
    ptn[dfn[u]] = u;
    if(son[u]) dfs2(son[u], tp);
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(v == son[u] || v == fa[u]) continue;
        dfs2(v, v);
    }
}

struct Node {
    LL val, add;
} sgt[MAXN << 2];

inline void pushdown(int o, int l, int r) {
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    if(sgt[o].add) {
        sgt[lch].add += sgt[o].add;
        sgt[rch].add += sgt[o].add;
        sgt[lch].val += sgt[o].add * (mid - l + 1);
        sgt[rch].val += sgt[o].add * (r - mid);
        sgt[o].add = 0;
    }
}

inline void build(int o, int l, int r) {
    if(l == r) {
        sgt[o].val = w[ptn[l]];
        return;
    }
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    build(lch, l, mid);
    build(rch, mid + 1, r);
    sgt[o].val = sgt[lch].val + sgt[rch].val;
}

inline void add(int o, int l, int r, int ll, int rr, LL v) {
    if(l >= ll && r <= rr) {
        sgt[o].add += v;
        sgt[o].val += v * (r - l + 1);
        return;
    }
    pushdown(o, l, r);
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    if(ll <= mid) add(lch, l, mid, ll, rr, v);
    if(rr > mid) add(rch, mid + 1, r, ll, rr, v);
    sgt[o].val = sgt[lch].val + sgt[rch].val;
}

inline LL query(int o, int l, int r, int ll, int rr) {
    if(l >= ll && r <= rr) {
        return sgt[o].val;
    }
    pushdown(o, l, r);
    LL res = 0;
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    if(ll <= mid) res += query(lch, l, mid, ll, rr);
    if(rr > mid) res += query(rch, mid + 1, r, ll, rr);
    return res;
}

inline void addpoint(int x, LL v) {
    add(1, 1, n, dfn[x], dfn[x], v);
}

inline void addsubtree(int x, LL v) {
    add(1, 1, n, dfn[x], dfn[x] + siz[x], v);
}

inline LL querychain(int x) {
    int tp = top[x];
    LL res = 0;
    while(x) {
        res += query(1, 1, n, dfn[tp], dfn[x]);
        x = fa[tp];
        tp = top[x];
    }
    return res;
}

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= n; i++) w[i] = readint();
    for(int i = 1; i < n; i++) {
        ut = readint(); vt = readint();
        addedge(ut, vt);
    }
    dfs1(1);
    dfs2(1, 1);
    build(1, 1, n);
    while(m--) {
        op = readint();
        xt = readint();
        if(op != 3) at = readint();
        if(op == 1) {
            addpoint(xt, at);
        }
        if(op == 2) {
            addsubtree(xt, at);
        }
        if(op == 3) {
            printf("%lld\n", querychain(xt));
        }
    }
    return 0;
}
[POJ3683]Priest John’s Busiest Day 题解 & 2-SAT算法的优化

[POJ3683]Priest John’s Busiest Day 题解 & 2-SAT算法的优化

题目地址:POJ:3683 — Priest John’s Busiest Day

题目描述

John is the only priest in his town. September 1st is the John’s busiest day in a year because there is an old legend in the town that the couple who get married on that day will be forever blessed by the God of Love. This year N couples plan to get married on the blessed day. The i-th couple plan to hold their wedding from time Si to time Ti. According to the traditions in the town, there must be a special ceremony on which the couple stand before the priest and accept blessings. The i-th couple need Di minutes to finish this ceremony. Moreover, this ceremony must be either at the beginning or the ending of the wedding (i.e. it must be either from Si to Si + Di, or from Ti – Di to Ti). Could you tell John how to arrange his schedule so that he can present at every special ceremonies of the weddings.
Note that John can not be present at two weddings simultaneously.
每个婚礼各有两段时间可以选择,要求每对新人的婚礼的时间不能重叠,求可行方案。

输入输出格式

输入格式:
The first line contains a integer N ( 1 ≤ N ≤ 1000).
The next N lines contain the Si, Ti and Di. Si and Ti are in the format of hh:mm.

输出格式:
The first line of output contains “YES” or “NO” indicating whether John can be present at every special ceremony. If it is “YES”, output another N lines describing the staring time and finishing time of all the ceremonies.

输入输出样例

输入样例#1:

2
08:00 09:00 30
08:15 09:00 20

输出样例#1:

YES
08:00 08:30
08:40 09:00

题解

本题同样是比较裸的2-SAT题目,只是利用本题来讲解2-SAT算法的优化如何实现。下面的内容也参考了《2-SAT算法浅析》(华师一附中 赵爽)的内容。
首先是建图的部分,我们对于任意一对可能冲突的时间段分别建边即可。剩下的事情就是跑一遍2-SAT。
我们知道,2-SAT图中的有向边表示的是一种“必须选择”的关系,那么如果图中有一个强连通分量,这个SCC中只要选中了就比如选里面所有的点。那么原来看上去很大的图就可以直接缩点成一个比较小的图,我们再考虑在小图上的工作。注意,缩点后的图中,SCC间的边要反向,原因将会在之后提到。
首先,如果有对立点存在于同一个SCC中,那么无解。
接着我们考虑利用这个有向无环图的拓扑序获得答案。我们先选中一个拓扑序列中未标记的点,然后DFS处理这个点的对立点所在的SCC。既然对立点所在SCC不能被选中,那么要求必须选中它的点就也不会选中,所以在原图的边的反向上,DFS应当沿着入边一路处理到入度为0的点,这里建反边的优势就体现出来了。重复这一工作,直到所有点都有一个标记“选中”或“不选”。
接着按照所在SCC的标记来输出可行解即可。

代码

以下代码借鉴了「POJ3683」Priest John’s Busiest Day – 2-SAT – hzwer.com的写法,感谢hzwer学长。

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

#include <vector>
#include <stack>
#include <queue>
#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;
    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 = 2005;

std::vector<int> gra[MAXN], gra2[MAXN];
int n, begin[MAXN], end[MAXN], mark[MAXN], deg[MAXN];
int dfn[MAXN], low[MAXN], tot, scc, belong[MAXN], op[MAXN];
std::stack<int> sta;
std::queue<int> que;
bool insta[MAXN];

inline bool notconflict(int x, int y) {
    return begin[x] >= end[y] || begin[y] >= end[x];
}

inline void tarjan(int u) {
    dfn[u] = low[u] = ++tot;
    sta.push(u); insta[u] = true;
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(!dfn[v]) {
            tarjan(v);
            low[u] = std::min(low[u], low[v]);
        } else if(insta[v]) {
            low[u] = std::min(low[u], dfn[v]);
        }
    }
    if(low[u] == dfn[u]) {
        int t = 0; scc++;
        while(t != u) {
            t = sta.top(); sta.pop();
            insta[t] = false;
            belong[t] = scc;
        }
    }
}

inline void dfs(int u) {
    if(mark[u]) return;
    mark[u] = -1;
    for(int i = 0; i < gra2[u].size(); i++) {
        int v = gra2[u][i];
        dfs(v);
    }
}

inline void toposort() {
    for(int i = 1; i <= scc; i++) {
        if(!deg[i]) que.push(i);
    }
    while(!que.empty()) {
        int u = que.front(); que.pop();
        if(mark[u]) continue;
        mark[u] = 1;
        dfs(op[u]);
        for(int i = 0; i < gra2[u].size(); i++) {
            int v = gra2[u][i];
            deg[v]--;
            if(!deg[v]) que.push(v);
        }
    }
}

int main() {
    n = readint();
    for(int i = 1; i <= n; i++) {
        begin[i << 1] = readint() * 60 + readint();
        end[(i << 1) - 1] = readint() * 60 + readint();
        int last = readint();
        end[i << 1] = begin[i << 1] + last;
        begin[(i << 1) - 1] = end[(i << 1) - 1] - last;
    }
    for(int i = 1; i <= n; i++) {
        for(int j = i + 1; j <= n; j++) {
            int a1 = i << 1, a2 = (i << 1) - 1, b1 = j << 1, b2 = (j << 1) - 1;
            if(!notconflict(a1, b1)) {
                gra[a1].push_back(b2);
                gra[b1].push_back(a2);
            }
            if(!notconflict(a1, b2)) {
                gra[a1].push_back(b1);
                gra[b2].push_back(a2);
            }
            if(!notconflict(a2, b1)) {
                gra[a2].push_back(b2);
                gra[b1].push_back(a1);
            }
            if(!notconflict(a2, b2)) {
                gra[a2].push_back(b1);
                gra[b2].push_back(a1);
            }
        }
    }
    for(int i = 1; i <= n << 1; i++) {
        if(!dfn[i]) tarjan(i);
    }
    for(int i = 1; i <= n; i++) {
        if(belong[i << 1] == belong[(i << 1) - 1]) {
            puts("NO");
            return 0;
        } 
        op[belong[i << 1]] = belong[(i << 1) - 1];
        op[belong[(i << 1) - 1]] = belong[i << 1];
    }
    puts("YES");
    for(int u = 1; u <= n << 1; u++) {
        for(int i = 0; i < gra[u].size(); i++) {
            int v = gra[u][i];
            if(belong[u] != belong[v]) {
                gra2[belong[v]].push_back(belong[u]);
                deg[belong[u]]++;
            }
        }
    }
    toposort();
    for(int i = 1; i <= n; i++) {
        if(mark[belong[i << 1]] == 1) {
            printf("%02d:%02d %02d:%02d\n", begin[i << 1] / 60, begin[i << 1] % 60, 
                end[i << 1] / 60, end[i << 1] % 60);
        } else {
            printf("%02d:%02d %02d:%02d\n", begin[(i << 1) - 1] / 60, begin[(i << 1) - 1] % 60, 
                end[(i << 1) - 1] / 60, end[(i << 1) - 1] % 60);
        }
    }
    return 0;
}
[WC2008]游览计划 题解 & 斯坦纳树类题目解法

[WC2008]游览计划 题解 & 斯坦纳树类题目解法

题目地址:洛谷:【P4294】[WC2008]游览计划 – 洛谷、BZOJ:Problem 2595. — [Wc2008]游览计划

题目描述

从未来过绍兴的小D有幸参加了Winter Camp 2008,他被这座历史名城的秀丽风景所吸引,强烈要求游览绍兴及其周边的所有景点。
为了保证安全与便利,主办方依据路况和治安状况,在非景点的一些方块内安排不同数量的志愿者;在景点内聘请导游(导游不是志愿者)。在选择旅游方案时,保证任意两个景点之间,存在一条路径,在这条路径所经过的每一个方块都有志愿者或者该方块为景点。既能满足选手们游览的需要,又能够让志愿者的总数最少。
现在,希望你能够帮助主办方找到一种最好的安排方案。

输入输出格式

输入格式:
第一行有两个整数,N和M,描述方块的数目。
接下来N行,每行有M个非负整数,如果该整数为0,则该方块为一个景点;
否则表示控制该方块至少需要的志愿者数目。相邻的整数用(若干个)空格隔开,
行首行末也可能有多余的空格。

输出格式:
由N+1行组成。第一行为一个整数,表示你所给出的方案中安排的志愿者总数目。
接下来N行,每行M个字符,描述方案中相应方块的情况:
‘_’(下划线)表示该方块没有安排志愿者;
‘o’(小写英文字母o)表示该方块安排了志愿者;
‘x’(小写英文字母x)表示该方块是一个景点;
注:请注意输出格式要求,如果缺少某一行或者某一行的字符数目和要求不一致(任何一行中,多余的空格都不允许出现),都可能导致该测试点不得分。

输入输出样例

输入样例#1:

4 4
0 1 1 0
2 5 5 1
1 5 5 1
0 1 1 0

输出样例#1:

6
xoox
___o
___o
xoox

说明

对于100%的数据,N,M,K≤10,其中K为景点的数目。输入的所有整数均在[0,2^16]的范围内

题解

本题的数据范围又好像是个状压DP,那就想个状压DP呗。要状压的状态应该是这个景点有没有被连接上,而DP状态也就可以设计成dp[i][j][S]表示在(i, j)处且上面的状态为S所需的最少志愿者数。a数组表示每个格子需要的志愿者数目。
现在转移变成了一个问题,首先我们可以考虑一个常规的枚举子集的思路:
dp[i][j][S] = \min_{T \subseteq S} \{ dp[i][j][T] + dp[i][j][S \wedge T] - a[i][j] \}
减一次的原因是这个数被加了两次。
但是不能老是从自己转移啊,四个相邻的格子也可以转移,但是有后效性。我们考虑SPFA转移,对于两个相邻的格子,有下面的转移
dp[i][j][S] = \min\{ dp[i'][j'][S] + a[i][j] \}
其中(i', j')(i, j)相邻。SPFA转移在求最小值的题目中很常见。
上面的这一通思路就是个裸的斯坦纳树。这种问题的模型是:有一个图,要求保留图中最少的边/最小的边权和使得某k个点相互连通。只是这个题需要把上述第二种转移更换为有边相连的点。

代码

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

#include <queue>

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;
    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 = 11, INF = 0x3f3f3f3f;
const int fix[2][4] = {{-1, 1, 0, 0}, {0, 0, -1, 1}};

struct Point {
    int x, y;
};

struct Data {
    int x, y, s;
} pre[MAXN][MAXN][1 << MAXN];

int n, m, k, mmp[MAXN][MAXN], dp[MAXN][MAXN][1 << MAXN], sx, sy;
std::queue<Point> que;
bool inque[MAXN][MAXN], ans[MAXN][MAXN];

inline void spfa(int s) {
    while(!que.empty()) {
        int x = que.front().x, y = que.front().y; que.pop(); inque[x][y] = false;
        for(int i = 0; i < 4; i++) {
            int nx = x + fix[0][i], ny = y + fix[1][i];
            if(nx < 1 || nx > n || ny < 1 || ny > m) continue;
            if(dp[nx][ny][s] > dp[x][y][s] + mmp[nx][ny]) {
                dp[nx][ny][s] = dp[x][y][s] + mmp[nx][ny];
                pre[nx][ny][s] = Data {x, y, s};
                if(!inque[nx][ny]) {
                    que.push(Point {nx, ny}); inque[nx][ny] = true;
                }
            }
        }
    }
}

inline void dfs(int x, int y, int s) {
    if(!pre[x][y][s].s) return;
    ans[x][y] = true;
    dfs(pre[x][y][s].x, pre[x][y][s].y, pre[x][y][s].s);
    if(pre[x][y][s].x == x && pre[x][y][s].y == y) dfs(x, y, s ^ pre[x][y][s].s);
}

int main() {
    n = readint(); m = readint();
    memset(dp, 0x3f, sizeof(dp));
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            if(!(mmp[i][j] = readint())) {
                dp[i][j][1 << (k++)] = 0;
                if(!sx) sx = i, sy = j;
            } 
        }
    }
    if(!k) {
        puts("0");
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) putchar('_');
            putchar('\n');
        }
        return 0;
    }
    for(int s = 1; s < (1 << k); s++) {
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                for(int t = s & (s - 1); t; t = s & (t - 1)) {
                    if(dp[i][j][s] > dp[i][j][t] + dp[i][j][s ^ t] - mmp[i][j]) {
                        dp[i][j][s] = dp[i][j][t] + dp[i][j][s ^ t] - mmp[i][j];
                        pre[i][j][s] = Data {i, j, t};
                    }
                }
                if(dp[i][j][s] < INF) que.push(Point {i, j}), inque[i][j] = true;
            }
        }
        spfa(s);
    }
    printf("%d\n", dp[sx][sy][(1 << k) - 1]);
    dfs(sx, sy, (1 << k) - 1);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            if(!mmp[i][j]) putchar('x');
            else if(ans[i][j]) putchar('o');
            else putchar('_');
        }
        putchar('\n');
    }
    return 0;
}