[题解]CLYZ2018省选训(bao)练(zha)模拟赛 Day 4

图的边数范围竟然没有明显地给出,真不良心。

题目

T1:BZOJ 2815 / ZJOI 2012 灾难 (catas)(拓扑排序+LCA)
T2:BZOJ 4069 / APIO 2015 巴厘岛的雕塑 (sculpture) (贪心+DP)
T3:BZOJ 1926 / SDOI 2010 粟粟的暑假 (susu)(1.二分答案+前缀和 2.主席树)

T1

分析

发现只给出了图的点数 N65534 N ≤ 65534 ,对于边的数量,题目只说「输入文件的大小不超过 1M 1 M 」,就尽量把数组开大吧,随便开了 106 10 6
可以想到建一棵灭绝树(森林),也就是说一个节点灭绝后它的子树灭绝。怎么建呢?
由于原图没有环,所以可以想到根据拓扑序建树。对于没有入度的点,直接作为根节点,对于剩下的点,假设现在考虑到了点 u u ,并且点u之前的灭绝树已经建好,此时考虑点 u u 应该作为哪个节点的子节点。可以想到,节点u灭绝,当且仅当节点 u u 的所有食物的LCA灭绝,所以,求出节点u的所有食物的LCA v v 之后,就可以把v作为 u u 的父亲,并初始化u的倍增数组(倍增LCA支持添加叶子节点)。
建树后,每个节点的 size s i z e 1 1 就是该节点的灾难值。

Source

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5, M = 1e6 + 5, LogN = 21;
int n, m, ecnt, nxt[M], adj[N], go[M], ecnt2, nxt2[M], adj2[N], go2[M],
fa[N][LogN], H, T, que[N], cnt[N], dep[N], sze[N], orz[N], ecnt3, nxt3[M],
adj3[N], go3[M];
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; cnt[go[ecnt] = v]++;
}
void add_edge2(int u, int v) {
    nxt2[++ecnt2] = adj2[u]; adj2[u] = ecnt2; go2[ecnt2] = v;
}
void add_edge3(int u, int v) {
    nxt3[++ecnt3] = adj3[u]; adj3[u] = ecnt3; orz[go3[ecnt3] = v]++;
}
int lca(int u, int v) {
    int i; if (dep[u] < dep[v]) swap(u, v);
    for (i = 19; i >= 0; i--) {
        if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
        if (u == v) return u;
    }
    for (i = 19; i >= 0; i--)
        if (fa[u][i] != fa[v][i])
            u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}
void cxandcyxbaosongqinghua() {
    int i; for (i = 1; i <= n; i++) if (!cnt[i]) que[++T] = i;
    while (H < T) {
        int u = que[++H]; int w = go2[adj2[u]];
        for (int e = adj[u], v; e; e = nxt[e])
            if (!(--cnt[v = go[e]])) que[++T] = v;
        for (int e = adj2[u]; e; e = nxt2[e])
            w = lca(w, go2[e]); if (fa[u][0] = w) add_edge3(w, u);
        dep[u] = dep[w] + 1;
        for (i = 0; i <= 18; i++) fa[u][i + 1] = fa[fa[u][i]][i];
    }
}
void dfs(int u) {
    sze[u] = 1;
    for (int e = adj3[u], v; e; e = nxt3[e])
        dfs(v = go3[e]), sze[u] += sze[v];
}
int main() {
    int i, j, t, x; n = read();
    for (i = 1; i <= n; i++) while (x = read())
        add_edge(x, i), add_edge2(i, x); cxandcyxbaosongqinghua();
    for (i = 1; i <= n; i++) if (!orz[i]) dfs(i);
    for (i = 1; i <= n; i++) printf("%d\n", sze[i] - 1);
    return 0;
}

T2

分析

直接DP感觉特别不可做(因为按位取或不满足最优化原理)。
而求最大/小与/或/异或和,可以贪心,在二进制意义下从高到低确定答案的每一位。在此题中要求最小或和,因此从高往低考虑到每一位时,这一位能为0就为 0 0 ,否则就为1
而如何判断能否为 0 0 ,就可以DP了。设当前考虑到第x位(最低位为第 0 0 位),当前结果为ans ans a n s 的第 0 0 位到第x1位都暂时定为 0 0 ):
下面定义一个or(a,b,k),表示 a a 的第k位到最高位和 b b 的第k位到最高位按位或(得出结果的第 0 0 位到第k1位都为 0 0 ):

or(a,b,k)=(a2korb2k)×2k

f[i][j] f [ i ] [ j ] 表示到第 i i 个雕塑,分成j段,并且对于任意的 h>x h > x ,如果 ans a n s 的第 h h 位为0,那么这 j j 段的或和的第h位也必须为 0 0 ,在这样的限制下,第x位能否为 0 0 。边界为f[0][0]=true
转移即枚举上一段的开头 k k ,如果存在一个k同时满足这两个条件:
1、 f[k][j1]=true f [ k ] [ j − 1 ] = t r u e ,即如果到第 k k 个雕塑,分成j1段时第 x x 位能为0,到第 i i 个雕塑分成j段时第 x x 位才有可能为0
2、设 sum[] s u m [ ] 为雕塑年龄的前缀和,条件为 sum[i]sum[k] s u m [ i ] − s u m [ k ] 的第 x x 位为0
3、对于任意的 h>x h > x ,如果 ans a n s 的第 h h 位为0,那么 sum[i]sum[k] s u m [ i ] − s u m [ k ] 的第 h h 位也必须为0,也就是:
or(sum[i]sum[k],ans,x)=ans o r ( s u m [ i ] − s u m [ k ] , a n s , x ) = a n s
满足这 3 3 个条件则f[i][j]=true
如果最后存在一个 AiB A ≤ i ≤ B 使得 f[n][i]=true f [ n ] [ i ] = t r u e ,则第 x x 为定为0
时间复杂度为 O(n3logni=1Yi) O ( n 3 log ⁡ ∑ i = 1 n Y i ) ,无法通过 2000 2000 的数据点。
但是 2000 2000 的数据点的特性是 A=1 A = 1 ,因此设 g[i] g [ i ] 表示到了第 i i 位,满足条件并且第x位为 0 0 ,最少需要分成几段,g[0]=0
这时候转移条件和 f f 基本相似,即枚举上一段的开头j,如果这个 j j 满足条件,则g[i]=min(g[i],g[j]+1)
最后如果 g[n]B g [ n ] ≤ B ,那么第 x x 位定为1
因此 A=1 A = 1 时复杂度 O(n2logni=1Yi) O ( n 2 log ⁡ ∑ i = 1 n Y i )

Source

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 2005, INF = 0x3f3f3f3f;
int n, A, B, a[N], g[N]; bool f[N][N];
ll sum[N], ans;
ll Or(ll x, ll y, int k) {
    x >>= k; y >>= k; return (x | y) << k;
}
bool _(int x) {
    int i, j, k; for (i = 0; i <= n; i++) for (j = 0; j <= n; j++)
        f[i][j] = 0; f[0][0] = 1;
    for (j = 1; j <= B; j++) for (i = 1; i <= n; i++)
    for (k = j - 1; k < i; k++) if (f[k][j - 1] &&
        Or(sum[i] - sum[k], ans, x) == ans)
            f[i][j] = 1;
    for (i = A; i <= B; i++) if (f[n][i]) return 1;
    return 0;
}
bool __(int x) {
    int i, j; memset(g, INF, sizeof(g)); g[0] = 0;
    for (i = 1; i <= n; i++) for (j = 0; j < i; j++)
        if (Or(sum[i] - sum[j], ans, x) == ans)
            g[i] = min(g[i], g[j] + 1);
    return g[n] <= B;
}
bool Try(int x) {
    if (A == 1) return __(x);
    return _(x);
}
int main() {
    int i; n = read(); A = read(); B = read();
    for (i = 1; i <= n; i++) sum[i] = sum[i - 1] + (a[i] = read());
    for (i = 43; i >= 0; i--) if (!Try(i)) ans |= 1ll << i;
    cout << ans << endl;
    return 0;
}

T3

分析

考虑一种暴力做法:把给定的子矩形内的所有书取出来,按照厚度从大到小排序,然后贪心从左往右考虑,如果排序之后前 k k 本书的厚度之和小于H,前 k+1 k + 1 本书的厚度大于等于 H H ,那么询问的结果就是k+1。否则如果给定子矩形内所有书的厚度之和小于 H H ,那么无解。
而此题从数据点上,实际是两个问题:
1、一个R×C的矩形,每次询问一个子矩形的结果, R,C200 R , C ≤ 200
2、一个 C C 个数的序列,每次询问一个区间的结果,C5×105
1:二维前缀和+二分答案。
cnt[i][j][k] c n t [ i ] [ j ] [ k ] 表示行号在 [1,i] [ 1 , i ] ,列号在 [1,j] [ 1 , j ] ,厚度大于等于 k k 的书的数目
sum[i][j][k]表示行号在 [1,i] [ 1 , i ] ,列号在 [1,j] [ 1 , j ] ,厚度大于等于 k k 的书的厚度之和
每一次询问二分最小厚度,求出值t,表示满足条件的情况下,最大的最小厚度。
然而由于有相同厚度,因此还要计算出厚度为 t t 的书有多少本没有用上
也就是,询问结果为:

cnts(x1,y1,x2,y2,t)sums(x1,y1,x2,y2,t)Ht

其中 cnts(x1,y1,x2,y2,t) c n t s ( x 1 , y 1 , x 2 , y 2 , t ) 表示左上角为 (x1,y1) ( x 1 , y 1 ) ,右下角为 (x2,y2) ( x 2 , y 2 ) 的矩形内厚度大于等于 t t 的书的数量(可以用cnt得出), sums s u m s 同理。
2:主席树上二分。
主席树的每个节点上记录:
cnt c n t :该前缀版本中,这个节点对应厚度区间内的书的数量。
sum s u m :该前缀版本中,这个节点对应厚度区间内的书的厚度之和。
如果一个询问区间内所有书的厚度之和小于 H H ,那么无解。
否则在主席树上二分。设query(l,r,h)表示询问 [y1,y2] [ y 1 , y 2 ] 中,用厚度为 [l,r] [ l , r ] 的书,达到 H H 的高度至少需要多少本书。
分情况考虑:
1、l=r时:

query(l,r,h)=hl q u e r y ( l , r , h ) = ⌈ h l ⌉

2、 lr l ≠ r ,设 mid=l+r2 m i d = ⌊ l + r 2 ⌋ ri r i 等于该询问中,厚度为 [mid+1,r] [ m i d + 1 , r ] 的书的厚度之和(可以在主席树上得到),当 h>ri h > r i 时:
query(l,r,h)=query(l,mid,hri)+delta q u e r y ( l , r , h ) = q u e r y ( l , m i d , h − r i ) + d e l t a

其中 delta d e l t a 等于该询问中,厚度为 [mid+1,r] [ m i d + 1 , r ] 的书的数量,也可以在主席树上得到。
3、否则:
query(l,r,h)=query(mid+1,r,h) q u e r y ( l , r , h ) = q u e r y ( m i d + 1 , r , h )

注意卡空间……

Source

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int Cyx = 205, Pyz = 1005, Lpf = 5e5 + 5, LpfLogLpf = 1e7 + 5;
int n, m, q, orz[Cyx][Cyx], sum[Cyx][Cyx][Pyz], cnt[Cyx][Cyx][Pyz],
QAQ, rt[Lpf], Sum[Lpf];
struct cyx {
    int lc, rc, cnt, sum;
} T[LpfLogLpf];
void ins(int y, int &x, int l, int r, int p) {
    T[x = ++QAQ] = T[y]; T[x].cnt++; T[x].sum += p; if (l == r) return;
    int mid = l + r >> 1;
    if (p <= mid) ins(T[y].lc, T[x].lc, l, mid, p);
    else ins(T[y].rc, T[x].rc, mid + 1, r, p);
}
int OrzDalao(int l, int r, int x, int y, int k) {
    return sum[r][y][k] - sum[l - 1][y][k] -
        sum[r][x - 1][k] + sum[l - 1][x - 1][k];
}
int OrzCyx(int l, int r, int x, int y, int k) {
    return cnt[r][y][k] - cnt[l - 1][y][k] -
        cnt[r][x - 1][k] + cnt[l - 1][x - 1][k];
}
int query(int l, int r, int h, int p1, int p2) {
    if (l == r) return h / l + (h % l > 0);
    int mid = l + r >> 1, ri = T[T[p2].rc].sum - T[T[p1].rc].sum;
    if (h > ri) return query(l, mid, h - ri, T[p1].lc, T[p2].lc)
        + T[T[p2].rc].cnt - T[T[p1].rc].cnt;
    else return query(mid + 1, r, h, T[p1].rc, T[p2].rc);
}
int main() {
    int i, j, k; n = read(); m = read(); q = read(); if (n > 1) {
        for (i = 1; i <= n; i++) for (j = 1; j <= m; j++)
            orz[i][j] = read();
        for (i = 1; i <= n; i++) for (j = 1; j <= m; j++)
        for (k = 1; k <= 1000; k++) {
            cnt[i][j][k] = cnt[i - 1][j][k] + cnt[i][j - 1][k]
                - cnt[i - 1][j - 1][k] + (orz[i][j] >= k);
            sum[i][j][k] = sum[i - 1][j][k] + sum[i][j - 1][k]
                - sum[i - 1][j - 1][k] + (orz[i][j] >= k ? orz[i][j] : 0);
        }
        int l, r, x, y, h; while (q--) {
            l = read(); x = read(); r = read(); y = read(); h = read();
            int L = 1, R = 1000; while (L <= R) {
                int mid = L + R >> 1;
                if (OrzDalao(l, r, x, y, mid) >= h) L = mid + 1;
                else R = mid - 1;
            }
            if (!R) {printf("Poor QLW\n"); continue;}
            int maxd = OrzDalao(l, r, x, y, R), delta = (maxd - h) / R;
            printf("%d\n", OrzCyx(l, r, x, y, R) - delta);
        }
        return 0;
    }
    for (i = 1; i <= m; i++) k = read(), ins(rt[i - 1], rt[i], 1, 1000, k),
        Sum[i] = Sum[i - 1] + k;
    int l, r, h; while (q--) {
        read(); l = read(); read(); r = read(); h = read();
        if (Sum[r] - Sum[l - 1] < h) {printf("Poor QLW\n"); continue;}
        printf("%d\n", query(1, 1000, h, rt[l - 1], rt[r]));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值