图的边数范围竟然没有明显地给出,真不良心。
题目
T1:BZOJ 2815 / ZJOI 2012 灾难 (catas)(拓扑排序+LCA)
T2:BZOJ 4069 / APIO 2015 巴厘岛的雕塑 (sculpture) (贪心+DP)
T3:BZOJ 1926 / SDOI 2010 粟粟的暑假 (susu)(1.二分答案+前缀和 2.主席树)
T1
分析
发现只给出了图的点数
N≤65534
N
≤
65534
,对于边的数量,题目只说「输入文件的大小不超过
1M
1
M
」,就尽量把数组开大吧,随便开了
106
10
6
。
可以想到建一棵灭绝树(森林),也就是说一个节点灭绝后它的子树灭绝。怎么建呢?
由于原图没有环,所以可以想到根据拓扑序建树。对于没有入度的点,直接作为根节点,对于剩下的点,假设现在考虑到了点
u
u
,并且点之前的灭绝树已经建好,此时考虑点
u
u
应该作为哪个节点的子节点。可以想到,节点灭绝,当且仅当节点
u
u
的所有食物的LCA灭绝,所以,求出节点的所有食物的LCA
v
v
之后,就可以把作为
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
0
,就可以DP了。设当前考虑到第位(最低位为第
0
0
位),当前结果为(
ans
a
n
s
的第
0
0
位到第位都暂时定为
0
0
):
下面定义一个,表示
a
a
的第位到最高位和
b
b
的第位到最高位按位或(得出结果的第
0
0
位到第位都为
0
0
):
f[i][j] f [ i ] [ j ] 表示到第 i i 个雕塑,分成段,并且对于任意的 h>x h > x ,如果 ans a n s 的第 h h 位为,那么这 j j 段的或和的第位也必须为 0 0 ,在这样的限制下,第位能否为 0 0 。边界为。
转移即枚举上一段的开头 k k ,如果存在一个同时满足这两个条件:
1、 f[k][j−1]=true f [ k ] [ j − 1 ] = t r u e ,即如果到第 k k 个雕塑,分成段时第 x x 位能为,到第 i i 个雕塑分成段时第 x x 位才有可能为。
2、设 sum[] s u m [ ] 为雕塑年龄的前缀和,条件为 sum[i]−sum[k] s u m [ i ] − s u m [ k ] 的第 x x 位为。
3、对于任意的 h>x h > x ,如果 ans a n s 的第 h h 位为,那么 sum[i]−sum[k] s u m [ i ] − s u m [ k ] 的第 h h 位也必须为,也就是:
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 个条件则。
如果最后存在一个 A≤i≤B A ≤ i ≤ B 使得 f[n][i]=true f [ n ] [ i ] = t r u e ,则第 x x 为定为。
时间复杂度为 O(n3log∑ni=1Yi) O ( n 3 log ∑ i = 1 n Y i ) ,无法通过 2000 2000 的数据点。
但是 2000 2000 的数据点的特性是 A=1 A = 1 ,因此设 g[i] g [ i ] 表示到了第 i i 位,满足条件并且第位为 0 0 ,最少需要分成几段,。
这时候转移条件和 f f 基本相似,即枚举上一段的开头,如果这个 j j 满足条件,则。
最后如果 g[n]≤B g [ n ] ≤ B ,那么第 x x 位定为。
因此 A=1 A = 1 时复杂度 O(n2log∑ni=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
本书的厚度之和小于,前
k+1
k
+
1
本书的厚度大于等于
H
H
,那么询问的结果就是。否则如果给定子矩形内所有书的厚度之和小于
H
H
,那么无解。
而此题从数据点上,实际是两个问题:
1、一个的矩形,每次询问一个子矩形的结果,
R,C≤200
R
,
C
≤
200
。
2、一个
C
C
个数的序列,每次询问一个区间的结果,。
1:二维前缀和+二分答案。
cnt[i][j][k]
c
n
t
[
i
]
[
j
]
[
k
]
表示行号在
[1,i]
[
1
,
i
]
,列号在
[1,j]
[
1
,
j
]
,厚度大于等于
k
k
的书的数目。
表示行号在
[1,i]
[
1
,
i
]
,列号在
[1,j]
[
1
,
j
]
,厚度大于等于
k
k
的书的厚度之和。
每一次询问二分最小厚度,求出值,表示满足条件的情况下,最大的最小厚度。
然而由于有相同厚度,因此还要计算出厚度为
t
t
的书有多少本没有用上。
也就是,询问结果为:
其中 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 的书的数量(可以用得出), sums s u m s 同理。
2:主席树上二分。
主席树的每个节点上记录:
cnt c n t :该前缀版本中,这个节点对应厚度区间内的书的数量。
sum s u m :该前缀版本中,这个节点对应厚度区间内的书的厚度之和。
如果一个询问区间内所有书的厚度之和小于 H H ,那么无解。
否则在主席树上二分。设表示询问 [y1,y2] [ y 1 , y 2 ] 中,用厚度为 [l,r] [ l , r ] 的书,达到 H H 的高度至少需要多少本书。
分情况考虑:
1、时:
2、 l≠r 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 时:
其中 delta d e l t a 等于该询问中,厚度为 [mid+1,r] [ m i d + 1 , r ] 的书的数量,也可以在主席树上得到。
3、否则:
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;
}