标签: 二分答案

[HAOI2008]木棍分割 题解

[HAOI2008]木棍分割 题解

题目地址:洛谷:【P2511】[HAOI2008]木棍分割 – 洛谷、BZOJ:Problem 1044. — [HAOI2008]木棍分割

题目描述

有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007。

输入输出格式

输入格式:
输入文件第一行有2个数n,m. 接下来n行每行一个正整数Li,表示第i根木棍的长度.

输出格式:
输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.

输入输出样例

输入样例#1:

3 2                           
1 
1
10

输出样例#1:

10 2

说明

两种砍的方法: (1)(1)(10)和(1 1)(10)
数据范围
n<=50000, 0<=m<=min(n-1,1000).
1<=Li<=1000.

题解

第一问的答案可以二分答案得到,每次验证的时候贪心地让每一段尽量长,从而获得最小切割次数即可。
第二问需要用动态规划的想法,设计状态$dp[i][j]$为前$i$个木棍切成$j$段的方案数,转移为
$$ dp[i][j] = \sum_{1 \leq j < i, sum_i – sum_k \leq ans} dp[k][j-1] $$
然而我们这样开两维空间不可以,因此考虑把第二维滚动了。时间复杂度的问题,我们考虑转移的时候是对上一维区间求和,注意到区间的左端点是单调的,因此可以在外面维护一个可以转移的区间以及它们的和来做转移,将时间优化至$O(nm)$。
本题略有点卡常,减少取模次数即可。

代码

// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#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();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 50005, MO = 10007;

int n, m, ans, ans2, a[MAXN], sum[MAXN], dp[MAXN][2];

inline bool check(int mid) {
    int sum = 0, cnt = 0;
    for(int i = 1; i <= n; i++) {
        if(a[i] > mid) return false;
        sum += a[i];
        if(sum > mid) {
            cnt++; sum = a[i];
        }
    }
    return cnt <= m;
}

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= n; i++) {
        a[i] = readint(); sum[i] = sum[i - 1] + a[i];
    }
    int l = 0, r = 1e9, mid;
    while(r - l > 1) {
        mid = (l + r) >> 1;
        if(check(mid)) r = mid; else l = mid;
    }
    ans = r;
    for(int i = 1; i <= n; i++) {
        if(sum[i] <= ans) dp[i][0] = 1; else break;
    }
    int now = 1;
    for(int j = 1; j <= m; j++) {
        int lsum = 0, ll = 1;
        for(int i = 1; i <= n; i++) {
            while(ll < i && sum[i] - sum[ll] > ans) {
                lsum -= dp[ll][now ^ 1]; lsum = (lsum + MO) % MO; ll++;
            }
            dp[i][now] = lsum;
            lsum = (lsum + dp[i][now ^ 1]) % MO;
        }
        ans2 = (ans2 + dp[n][now]) % MO;
        now ^= 1;
    }
    printf("%d %d", ans, ans2);
    return 0;
}
[POI2005]KOS-Dicing 题解

[POI2005]KOS-Dicing 题解

题目地址:洛谷:【P3425】[POI2005]KOS-Dicing – 洛谷、BZOJ:Problem 1532. — [POI2005]Kos-Dicing

题目描述

掷骰子是一种双人游戏,它的结果是完全随机的。最近它在整个Byteotia变得非常流行。在Byteotia的首都甚至有一个特别的掷骰子业余爱好者俱乐部。俱乐部的老主顾们花时间互相聊天并每隔一阵子就和一个随机选择的对手玩这他们最喜欢的游戏。一天中赢得最多游戏的人会得到“幸运者”头衔。有时晚上俱乐部很安静,只有很少的比赛。这是哪怕赢一场也能获得“幸运者”头衔的时间。
很久很久以前有一个很不走运的人,叫Byteasar,赢得了这个光荣的头衔。他被深深地震惊了以至于完全忘了他已经赢了多少场。现在他想知道他有多幸运,以及幸运之神是否最终会向他微笑——也许他的运气会变好?他确切地知道在那个幸运的晚上有多少场游戏以及是谁玩的。然而,他不知道结果。Byteasar希望查明至少要赢几场才能获得“幸运者”头衔。做个好人,帮他满足他的好奇心吧!
任务:写一个程序:
对于每场游戏从stdin读入这场游戏的一对玩家 找到最小的数k,使得存在一个游戏结果的集合,其中赢最多的玩家赢了k场。向stdout输出数k和找到的集合中游戏的结果

题意简述

Dicing 是一个两人玩的游戏,现在有一个非常不走运的家伙,他想成为那个玩的最好的人,他现在知道了所有比赛的安排,他想知道,在最好的情况下,他最少只需要赢几场就可以赢得冠军,即他想知道比赛以后赢的最多的那个家伙最少会赢多少场.

输入输出格式

输入格式:
stdin的第一行有一个一对由一个空格分开整数n和m (1 <= n, m <= 10000) 。n表示玩家数,m表示游戏数。玩家从1到n编号。在接下来的m行中有每场游戏的一对玩家的编号,由一个空格分开,描述了游戏的序列。一对玩家可能会在这个序列中多次出现。

输出格式:
stdout的第一行应该包含一个确定的数k。对于在输入的第i行指定的一对玩家a, b,如果在找到的结果集合中a胜过b,则在输出的第i行输出1, 否则输出0.

输入输出样例

输入样例#1:

4 4
1 2
1 3
1 4
1 2

输出样例#1:

1
0
0
0
1

题解

考虑二分答案然后判定。判定可以采用网络流,建立源→每场游戏→游戏玩家→汇的网络,源→游戏及游戏→玩家的边容量为1,玩家→汇的边容量为二分的答案。如果答案是x,说明游戏中每个人胜出的次数都不大于x,因此只需在该网络上判定是否满流,满流说明该答案可行。
复杂度玄学。

代码

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

#include <algorithm>
#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;
    register char c = fgc();
    while(!isdigit(c)) {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(isdigit(c)) {
        res = (res << 1) + (res << 3) + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 20005, INF = 1e9;

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

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

int level[MAXN];

inline bool bfs(int s, int t) {
    memset(level, -1, sizeof(level));
    std::queue<int> que;
    que.push(s); level[s] = 0;
    while(!que.empty()) {
        int u = que.front(); que.pop();
        for(int i = head[u]; ~i; i = gra[i].nxt) {
            int v = gra[i].to;
            if(gra[i].cap && level[v] == -1) {
                level[v] = level[u] + 1;
                if(v == t) return true;
                que.push(v);
            }
        }
    }
    return level[t] != -1;
}

int cur[MAXN];

inline int dfs(int u, int t, int left) {
    if(u == t || !left) return left;
    int flow = 0;
    for(int &i = cur[u]; ~i; i = gra[i].nxt) {
        int v = gra[i].to;
        if(level[v] == level[u] + 1 && gra[i].cap) {
            int f = dfs(v, t, std::min(left, gra[i].cap));
            if(f) {
                flow += f; left -= f;
                gra[i].cap -= f; gra[i ^ 1].cap += f;
                if(!left) break;
            }
        }
    }
    return flow;
}

inline int dinic(int s, int t) {
    int flow = 0;
    while(bfs(s, t)) {
        memcpy(cur, head, sizeof(head));
        int f;
        while(f = dfs(s, t, INF)) flow += f;
    }
    return flow;
}

int n, m, a[MAXN], b[MAXN], S, T;

inline bool check(int x) {
    tot = 0; memset(head, -1, sizeof(head));
    for(int i = 1; i <= n; i++) {
        addedge(i, T, x);
    }
    for(int i = 1; i <= m; i++) {
        addedge(S, i + n, 1);
        addedge(i + n, a[i], 1);
        addedge(i + n, b[i], 1);
    }
    return dinic(S, T) >= m;
}

int main() {
    n = readint(); m = readint(); S = n + m + 1; T = S + 1;
    for(int i = 1; i <= m; i++) {
        a[i] = readint(); b[i] = readint();
    }
    int l = 0, r = m, mid;
    while(r - l > 1) {
        mid = (l + r) >> 1;
        if(check(mid)) r = mid; else l = mid;
    }
    printf("%d\n", r);
    check(r);
    for(int i = 1; i <= m; i++) {
        for(int j = head[i + n]; ~j; j = gra[j].nxt) {
            int v = gra[j].to;
            if(!gra[j].cap) {
                printf("%d\n", v == a[i]); break;
            }
        }
    }
    return 0;
}
[SDOI2010]粟粟的书架 题解

[SDOI2010]粟粟的书架 题解

题目地址:洛谷:【P2468】[SDOI2010]粟粟的书架 – 洛谷、BZOJ:Problem 1926. — [Sdoi2010]粟粟的书架

题目描述

幸福幼儿园B29班的粟粟是一个聪明机灵、乖巧可爱的小朋友,她的爱好是画画和读书,尤其喜欢Thomas H. Cormen的文章。粟粟家中有一个R行C列的巨型书架,书架的每一个位置都摆有一本书,上数第i行、左数第j列摆放的书有Pi,j页厚。
粟粟每天除了读书之外,还有一件必不可少的工作就是摘苹果,她每天必须摘取一个指定的苹果。粟粟家果树上的苹果有的高、有的低,但无论如何凭粟粟自己的个头都难以摘到。不过她发现,如果在脚下放上几本书,就可以够着苹果;她同时注意到,对于第i天指定的那个苹果,只要她脚下放置书的总页数之和不低于Hi,就一定能够摘到。
由于书架内的书过多,父母担心粟粟一天内就把所有书看完而耽误了上幼儿园,于是每天只允许粟粟在一个特定区域内拿书。这个区域是一个矩形,第i天给定区域的左上角是上数第x1i行的左数第y1i本书,右下角是上数第x2i行的左数第y2i本书。换句话说,粟粟在这一天,只能在这﹙x2i-x1i+1﹚×﹙y2i-y1i+1﹚本书中挑选若干本垫在脚下,摘取苹果。
粟粟每次取书时都能及时放回原位,并且她的书架不会再撤下书目或换上新书,摘苹果的任务会一直持续M天。给出每本书籍的页数和每天的区域限制及采摘要求,请你告诉粟粟,她每天至少拿取多少本书,就可以摘到当天指定的苹果。

题意简述

有一个矩阵,每次询问给一个子矩阵的范围,求该范围中最少取几个数使得它们的和不小于定值h。

输入输出格式

输入格式:
输入文件susu.in第一行是三个正整数R, C, M。
接下来是一个R行C列的矩阵,从上到下、从左向右依次给出了每本书的页数Pi,j。
接下来M行,第i行给出正整数x1i, y1i, x2i, y2i, Hi,表示第i天的指定区域是﹙x1i, y1i﹚与﹙x2i, y2i﹚间的矩形,总页数之和要求不低于Hi。
保证1≤x1i≤x2i≤R,1≤y1i≤y2i≤C。

输出格式:
输出文件susu.out有M行,第i行回答粟粟在第i天时为摘到苹果至少需要拿取多少本书。如果即使取走所有书都无法摘到苹果,则在该行输出“Poor QLW”(不含引号)。

输入输出样例

输入样例#1:

5 5 7
14 15 9 26 53
58 9 7 9 32
38 46 26 43 38
32 7 9 50 28
8 41 9 7 17
1 2 5 3 139
3 1 5 5 399
3 3 4 5 91
4 1 4 1 33
1 3 5 4 185
3 3 4 3 23
3 1 3 3 108

输出样例#1:

6
15
2
Poor QLW
9
1
3

输入样例#2:

1 10 7
14 15 9 26 53 58 9 7 9 32
1 2 1 9 170
1 2 1 9 171
1 5 1 7 115
1 1 1 10 228
1 4 1 4 45704571
1 1 1 1 1
1 7 1 8 16

输出样例#2:

6
7
3
10
Poor QLW
1
2

说明

【数据规模和约定】
对于10%的数据,满足R, C≤10;
对于20%的数据,满足R, C≤40;
对于50%的数据,满足R, C≤200,M≤200,000;
另有50%的数据,满足R=1,C≤500,000,M≤20,000;
对于100%的数据,满足1≤Pi,j≤1,000,1≤Hi≤2,000,000,000。

题解

强烈谴责这种二合一题目。
第一个50%,考虑求一个前缀和,统计大于等于k的值的数字和以及个数,这个前缀和的处理是O(1000n^2)的。对于每个查询,二分取出的数中最小的数字,check的时候在矩阵中找到大于等于它的数之和,验证是否不小于定值h。注意有可能最小的数字有多个,不一定全部取,可以求一下加起来比h大了多少,减掉没用的那部分。总复杂度是O(1000n^2)
第二个50%,矩阵变成了序列,自然对序列建主席树来查询。每次查询先判断一下右儿子够不够,不够再向左儿子递归。当递归到叶子节点的时候就是找到了最小值,要类似上面那样计算一下这个最小值要拿多少个。这一部分的复杂度是O(n \log 1000)

代码

// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#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(!isdigit(c)) {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(isdigit(c)) {
        res = (res << 1) + (res << 3) + c - '0';
        c = fgc();
    }
    return res * neg;
}

int n, m, q;

namespace Problem1 {
    const int MAXN = 205;

    int sum[MAXN][MAXN][1005];
    int p[MAXN][MAXN], cnt[MAXN][MAXN][1005];
    int x1, y1, x2, y2, h;

    inline int query(int x1, int y1, int x2, int y2, int h) {
        int l = 0, r = 1001, mid;
        while(r - l > 1) {
            mid = (l + r) >> 1;
            if(sum[x2][y2][mid] - sum[x1 - 1][y2][mid] - sum[x2][y1 - 1][mid] 
                + sum[x1 - 1][y1 - 1][mid] >= h) {
                l = mid;
            } else {
                r = mid;
            }
        }
        if(!l) return -1;
        int rem = sum[x2][y2][l] - sum[x1 - 1][y2][l] - sum[x2][y1 - 1][l] 
                + sum[x1 - 1][y1 - 1][l] - h,
            rcnt = cnt[x2][y2][l] - cnt[x1 - 1][y2][l] - cnt[x2][y1 - 1][l] 
                + cnt[x1 - 1][y1 - 1][l];
        return rcnt - rem / l;
    }

    int main() {
        for(register int i = 1; i <= n; i++) {
            for(register int j = 1; j <= m; j++) {
                p[i][j] = readint();
            }
        }
        for(register int i = 1; i <= n; i++) {
            for(register int j = 1; j <= m; j++) {
                for(register int k = 1; k <= 1000; k++) {
                    sum[i][j][k] = sum[i][j - 1][k] + sum[i - 1][j][k] - sum[i - 1][j - 1][k];
                    cnt[i][j][k] = cnt[i][j - 1][k] + cnt[i - 1][j][k] - cnt[i - 1][j - 1][k];
                    if(p[i][j] >= k) {
                        sum[i][j][k] += p[i][j]; cnt[i][j][k]++;
                    }
                }
            }
        }
        while(q--) {
            x1 = readint(); y1 = readint(); x2 = readint(); y2 = readint(); h = readint();
            int res = query(x1, y1, x2, y2, h);
            if(res != -1) printf("%d\n", res);
            else puts("Poor QLW");
        }
        return 0;
    }
}

namespace Problem2 {
    const int MAXN = 500005;

    int p[MAXN];

    struct Node {
        int lch, rch, cnt, sum;
    } tr[MAXN * 15];
    int rt[MAXN], tot;

    inline void insert(int &o, int l, int r, int x) {
        tr[++tot] = tr[o]; o = tot;
        tr[o].cnt++; tr[o].sum += x;
        if(l == r) return;
        int mid = (l + r) >> 1;
        if(x <= mid) insert(tr[o].lch, l, mid, x);
        else insert(tr[o].rch, mid + 1, r, x);
    }

    inline int query(int o1, int o2, int l, int r, int h) {
        if(l == r) return (h + l - 1) / l;
        int mid = (l + r) >> 1, rsum = tr[tr[o2].rch].sum - tr[tr[o1].rch].sum;
        if(h <= rsum) return query(tr[o1].rch, tr[o2].rch, mid + 1, r, h);
        else return tr[tr[o2].rch].cnt - tr[tr[o1].rch].cnt 
            + query(tr[o1].lch, tr[o2].lch, l, mid, h - rsum);
    }

    int x1, y1, x2, y2, h;

    int main() {
        for(register int i = 1; i <= m; i++) {
            p[i] = readint();
            rt[i] = rt[i - 1];
            insert(rt[i], 1, 1000, p[i]);
        }
        while(q--) {
            x1 = readint(); y1 = readint(); x2 = readint(); y2 = readint(); h = readint();
            if(tr[rt[y2]].sum - tr[rt[y1 - 1]].sum < h) puts("Poor QLW");
            else printf("%d\n", query(rt[y1 - 1], rt[y2], 1, 1000, h));
        }
        return 0;
    }
}

int main() {
    n = readint(); m = readint(); q = readint();
    if(n == 1) return Problem2::main();
    else return Problem1::main();
}