T1
Description
Sylvia是一个热爱学习的女孩子。
在平时的练习中,他总是能考到std以上的成绩,前段时间,他参加了一场练习赛,众所周知,机房是一个 的方阵。这天,他又打爆了std,感到十分无聊,便想要hack机房内同学的程序,他会挑选一整行或一整列的同学进行hack ( 而且每行每列只会hack一次 ),然而有些同学不是那么好惹,如果你hack了他两次,他会私下寻求解决,Sylvia十分害怕,只会hack他们一次。假设Sylvia的水平十分高超,每次hack都能成功,求他最 多能hack多少次?
Data Constraint
数据规模和约定
对于20%的数据 n<=10, x<=100
对于40%的数据 n<=20 , x<=400
对于100%的数据 n<=1000 , x<=4000
1<=x,y<=n且同一个点不会重复出现
想不到的水题。。。。
当天状态很差,看了这题没有任何想法。
事实上这是非常经典的二分图模型。
考虑左右各
n
n
n个点的二分图,对于每个不好惹的同学
(
x
,
y
)
(x,y)
(x,y)将左边的
x
x
x连向右边的
y
y
y,设这个图的最大独立集左边共选
a
a
a个点,右边共选
b
b
b个点,答案就是
n
(
n
−
a
)
+
n
(
n
−
b
)
=
n
(
2
n
−
(
a
+
b
)
)
n(n-a)+n(n-b)=n(2n-(a+b))
n(n−a)+n(n−b)=n(2n−(a+b))。
于是只需求出这个图的最大独立集即可,然鹅我不会匈牙利,我选择网络流。
点数边数什么的多开一点,不然可能随机RE。
Code:
#include <cstdio>
#include <cstring>
#include <cstdlib>
const int N = 2007, M = 18007, INF = 0x3f3f3f3f;
int min(int a, int b) { return a < b ? a : b; }
int n, m;
int S, T;
int tot = 1, st[N], to[M], nx[M], len[M];
void add(int u, int v, int w)
{
to[++tot] = v, nx[tot] = st[u], len[tot] = w, st[u] = tot;
to[++tot] = u, nx[tot] = st[v], len[tot] = 0, st[v] = tot;
}
int head, tail, que[N], dep[N];
int bfs()
{
memset(dep, 0, sizeof(dep));
head = 1, que[tail = 1] = S, dep[S] = 1;
while (head <= tail)
{
int u = que[head++];
for (int i = st[u]; i; i = nx[i])
if (len[i] > 0 && !dep[to[i]])
dep[to[i]] = dep[u] + 1, que[++tail] = to[i];
}
return dep[T];
}
int dinic(int u, int flow)
{
if (u == T) return flow;
int rest = flow, tmp;
for (int i = st[u]; i; i = nx[i])
if (len[i] > 0 && dep[to[i]] == dep[u] + 1)
{
tmp = dinic(to[i], min(rest, len[i]));
if (!tmp) dep[to[i]] = 0;
rest -= tmp, len[i] -= tmp, len[i ^ 1] += tmp;
}
return flow - rest;
}
int main()
{
//freopen("phalanx.in", "r", stdin);
//freopen("phalanx.out", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 1, u, v; i <= m; i++) scanf("%d%d", &u, &v), add(u, v + n, INF);
S = 0, T = 2 * n + 1;
for (int i = 1; i <= n; i++) add(S, i, 1), add(i + n, T, 1);
int ans = 0;
while (bfs())
while (int flow = dinic(S, INF))
ans += flow;
printf("%d\n", n * (2 * n - ans));
fclose(stdin);
fclose(stdout);
return 0;
}
T2
Description
由于小凯上次在找零问题上的疑惑,给大家在考场上带来了很大的麻烦,他决心好好学习数学
本次他挑选了位运算专题进行研究 他发明了一种叫做“小凯运算”的运算符:
a$b =( (a&b) + (a|b) )>>1
他为了练习,写了n个数在黑板上(记为a[i]) 并对任意相邻两个数进行“小凯运算”,把两数擦去,把结果留下 这样操作n-1次之后就只剩了1个数,求这个数可能是什么?
将答案从小到大顺序输出
Data Constraint
30% n<=10 0<=a[i]<=7
70% n<=150 0<=a[i]<=3
100% n<=150 0<=a[i]<=7
(良心水题)
a
&
b
+
a
∣
b
a \& b+a | b
a&b+a∣b?
我好像在初赛见过这东西。
那道题是让你求
0
≤
a
,
b
≤
31
0\le a,b \le 31
0≤a,b≤31时方程
a
b
=
(
a
&
b
)
(
a
∣
b
)
ab=(a \& b)(a | b)
ab=(a&b)(a∣b)的解数。
然鹅两题并没有什么相似之处,重要的是这个性质:
a
&
b
+
a
∣
b
=
a
+
b
a \& b+a | b=a+b
a&b+a∣b=a+b
证明比较easy。
首先,两个二进制数相加时,它俩互换一下相同位上的数,结果是一样的。
如果
a
,
b
a,b
a,b某一位上都是
1
1
1,那么
a
&
b
,
a
∣
b
a \& b,a | b
a&b,a∣b的那一位都是
1
1
1。如果如果
a
,
b
a,b
a,b有一位上
1
1
1,那么
a
&
b
a \& b
a&b那一位是
0
0
0,
a
∣
b
a | b
a∣b那一位是
1
1
1,互换一下,结果不变。如果
a
,
b
a,b
a,b某一位都是
0
0
0,
a
&
b
,
a
∣
b
a \& b,a | b
a&b,a∣b的那一位都是
0
0
0,相加都是0。
因此
a
&
b
+
a
∣
b
=
a
+
b
a \& b+a | b=a+b
a&b+a∣b=a+b。
题目就变成给你
n
n
n个数,每次可以选俩相邻的数
a
,
b
a,b
a,b合在一起变成
(
a
+
b
)
/
2
(a+b)/2
(a+b)/2,问你最终可能留下哪些数。
显然区间dp:设
f
i
,
j
,
k
=
1
f_{i,j,k}=1
fi,j,k=1表示区间
[
i
,
j
]
[i,j]
[i,j]可能出现
k
k
k,
f
i
,
j
,
k
=
0
f_{i,j,k}=0
fi,j,k=0表示区间
[
i
,
j
]
[i,j]
[i,j]不可能出现
k
k
k。一开始我只从区间两端转移,一拍就炸了。正确的转移是枚举
i
≤
t
≤
j
−
1
i \le t \le j-1
i≤t≤j−1,然后再枚举区间
[
i
,
t
]
[i,t]
[i,t]留下的哪个数
g
g
g,那么只要(
g
+
g+
g+区间
[
t
+
1
,
j
]
[t+1,j]
[t+1,j]留下的数)
=
2
k
=2k
=2k或
2
k
+
1
2k+1
2k+1,
f
i
,
j
,
k
=
1
f_{i,j,k}=1
fi,j,k=1。
最后枚举哪些
i
i
i满足
f
1
,
n
,
i
=
1
f_{1,n,i}=1
f1,n,i=1就行了。
跑满的话时间复杂度是 O ( 64 n 3 ) O(64n^3) O(64n3),大概 2 2 2亿,在 f i , j , k = 1 f_{i,j,k}=1 fi,j,k=1时就不再转移,可以比较快跑过去。
Code:
#include <cstdio>
#include <cstring>
#include <cstdlib>
const int N = 157, A = 20;
int n, a[N];
int f[N][N][A];
int main()
{
freopen("math.in", "r", stdin);
freopen("math.out", "w", stdout);
memset(f, 0, sizeof(f));
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", a + i), f[i][i][a[i]] = 1;
for (int l = n; l >= 1; l--)
for (int r = 1; r <= n; r++)
for (int k = 0; k <= 7; k++)
for (int i = l; i <= r - 1; i++)
{
if (f[l][r][k]) break;
for (int j = 0; j <= 7; j++)
if ((f[l][i][j] && f[i + 1][r][2 * k - j]) || (f[l][i][j] && f[i + 1][r][2 * k + 1 - j]))
{
f[l][r][k] = 1;
break;
}
}
for (int i = 0; i <= 7; i++) if (f[1][n][i]) printf("%d ", i);
fclose(stdin);
fclose(stdout);
return 0;
}
T3
Description
策策由于在noip2017考试当天去逛公园了,没能出现在考场上,转眼到了noip2018,策策的公园也悄然转变,策策能否克服诱惑,成功坐在考场上呢?
问题描述
策策同学特别喜欢逛公园,公园可以看做有n个景点的序列,每个景点会给策策带来di 的愉悦度,策策初始有x0 的愉悦度,然而愉悦度也是有上限的,他在每个景点的愉悦度上限为li ,策策想要从 l 到 r这一段景点中选择一段景点参观(从这一段的左端点逛到这一段的右端点),策策想知道他最终的愉悦度的最大值是多少,你能帮帮他吗?(区间可以为空,也就是说答案最小为x0 )
Sample Input
6 3
0 5 3 2 0 4
8 10 8 1 9 9
1 3 9
2 6 3
3 4 0
Sample Output
10
8
3
样例说明
询问1 初始愉悦度9 只逛第2个公园 9+5=14 大于l2 ans=10
询问2 初始愉悦度3 从2逛到3 3+5+3=11 大于l3 ans=8
询问3 初始愉悦度0 只逛第3个公园 ans=3
对于全部数据 n ≤ 40000 , q ≤ 40000 , d i ≤ 10000 , l i ≤ 1000000 n\le40000,q\le40000,d_i\le10000,l_i\le1000000 n≤40000,q≤40000,di≤10000,li≤1000000。
来自俄罗斯的分块!
这又是一道亦可赛艇的分块题,然鹅考试的时候连 O ( n q ) O(nq) O(nq)做法都想不到, O ( n q ) O(nq) O(nq)做法能水到 90 p t s 90pts 90pts…。
先不管询问的限制,设
F
(
l
,
r
,
x
0
)
F(l,r,x_0)
F(l,r,x0)表示以
x
0
x_0
x0为初始愉悦度,从
l
l
l走到
r
r
r最终的愉悦度。
这玩意是关于
x
0
x_0
x0单调的,也就是说有第一个性质:
若 a ≤ b a\le b a≤b,则 F ( l , r , a ) ≤ F ( l , r , b ) F(l,r,a)\le F(l,r,b) F(l,r,a)≤F(l,r,b)。
这个性质的证明显然。
光有这性质还不够,设 G ( l , r ) = F ( l , r , ∞ ) G(l,r)=F(l,r,\infty) G(l,r)=F(l,r,∞), S ( l , r ) = ∑ i = l r d i S(l,r)=\sum_{i=l}^{r}d_i S(l,r)=∑i=lrdi。
则有 F ( l , r , x 0 ) = m i n ( G ( l , r ) , x 0 + S ( l , r ) ) F(l,r,x_0)=min(G(l,r),x_0+S(l,r)) F(l,r,x0)=min(G(l,r),x0+S(l,r))。
若 G ( l , r ) < x 0 + S ( l , r ) G(l,r)<x_0+S(l,r) G(l,r)<x0+S(l,r),则必然会在某个点 i i i答案与 l i l_i li取 m i n min min,这样答案就等同于以无穷大的愉悦度出发。因为 F ( l , r , x 0 ) F(l,r,x_0) F(l,r,x0)和 G ( l , r ) G(l,r) G(l,r)分别会在某个位置愉悦度变成 l i l_i li,所以最终答案是一样的。反之若 x 0 + S ( l , r ) < G ( l , r ) x_0+S(l,r)<G(l,r) x0+S(l,r)<G(l,r),则最多只能获得 x 0 + S ( l , r ) x_0+S(l,r) x0+S(l,r)的愉悦度。由此得到上面的性质。
然后考虑把数列分块。
对于一个询问
[
l
,
r
]
[l,r]
[l,r],可能的答案有四种:
1.中间构成整块的区间中的一个区间。
2.左边不构成整块的区间中的一个区间。
3.右边不构成整块的区间中的一个区间。
4.跨过多个区间的一个区间。
先考虑1:
对于同一块中的两个区间
[
l
1
,
r
1
]
[l_1,r_1]
[l1,r1]和
[
l
2
,
r
2
]
[l_2,r_2]
[l2,r2],若
G
(
l
1
,
r
1
)
≤
G
(
l
2
,
r
2
)
G(l_1,r_1)\le G(l_2,r_2)
G(l1,r1)≤G(l2,r2)且
S
(
l
1
,
r
1
)
≤
S
(
l
2
,
r
2
)
S(l_1,r_1)\le S(l_2,r_2)
S(l1,r1)≤S(l2,r2),那么对于所有的
x
0
x_0
x0都有
F
(
l
1
,
r
1
,
x
0
)
≤
F
(
l
2
,
r
2
,
x
0
)
F(l_1,r_1,x_0)\le F(l_2,r_2,x_0)
F(l1,r1,x0)≤F(l2,r2,x0),也就是
[
l
1
,
r
1
]
[l_1,r_1]
[l1,r1]永远轮不上它,
[
l
2
,
r
2
]
[l_2,r_2]
[l2,r2]比它更优。这个可以由第二个性质得到。于是对于每个块,处理出其所有的子区间,由于块的长度是
O
(
n
)
O(\sqrt n)
O(n),子区间个数是
O
(
n
)
O(n)
O(n)的,我们只需要做一遍类似单调栈的过程就能去掉那些没用的区间。得到一个
G
G
G值严格递增,
S
S
S值严格递减的区间序列。每个块都如此做,复杂度
O
(
n
n
)
O(n\sqrt n)
O(nn)。
对于一个询问
x
0
x_0
x0,那么
G
G
G和
x
0
+
S
x_0+S
x0+S都具有单调性,因此可以通过二分找到最大的
G
G
G使
G
<
x
0
+
S
G<x_0+S
G<x0+S,设它在序列区间里是第
i
i
i个,那么答案就是
m
a
x
(
m
i
n
(
G
(
l
i
,
r
i
)
,
x
0
+
S
(
l
i
,
r
i
)
)
,
m
i
n
(
G
(
l
i
+
1
,
r
i
+
1
)
,
x
0
+
S
(
l
i
+
1
,
r
i
+
1
)
)
)
max(min(G(l_i,r_i),x_0+S(l_i,r_i)),min(G(l_{i+1},r_{i+1}),x_0+S(l_{i+1},r_{i+1})))
max(min(G(li,ri),x0+S(li,ri)),min(G(li+1,ri+1),x0+S(li+1,ri+1)))。
答案比较显然,证明略。
块的数量是
O
(
n
)
O(\sqrt n)
O(n),每次二分复杂度
O
(
l
o
g
n
)
O(logn)
O(logn),总共复杂度就是
O
(
n
⋅
l
o
g
n
)
O(\sqrt n · logn)
O(n⋅logn)。
考虑2,3:
采用
O
(
n
q
)
O(nq)
O(nq)的算法,扫一下这个区间,每次都把答案加上
d
i
d_i
di并和
l
i
l_i
li取
m
i
n
min
min,如果这样以后得到的答案小于
x
0
x_0
x0,那就从
x
0
x_0
x0重新走。小块的长度是
O
(
n
)
O(\sqrt n)
O(n),两个小块都做一遍复杂度是
O
(
n
)
O(\sqrt n)
O(n)。
考虑4:
我们只用考虑右端点在当前块的情况。
把答案区间拆成两个区间:这个块左端点以前紧靠着左端点的一个区间,这个块的某个前缀。
设前者答案是
y
y
y,那么我们可以将
y
y
y看作走这个块的某个前缀的初始愉悦度,用类似1的处理方法求出所有前缀,二分求出答案。
求完右端点在该块的答案之后,我们还要计算这个块应该给到下一块的
y
y
y。我们有三个选择,一个是把当前块全部走过,一个是以
x
0
x_0
x0为初始愉悦度走过该块的某个后缀,还有一个就是直接从
x
0
x_0
x0开始。由性质1的单调性我们可知,
y
y
y越大答案就会越大,我们只需要在三者中取最大的一个即可。
这里有两次二分,总共复杂度是
O
(
n
⋅
l
o
g
n
)
O(\sqrt n · logn)
O(n⋅logn)。
所以总的复杂度是: O ( n n + q n ⋅ l o g n ) O(n\sqrt n+q\sqrt n · logn) O(nn+qn⋅logn),常数大可能过不了。
Code:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int N = 4e4 + 7, RT = 2e2 + 7, INF = (1ll << 30);
inline int read()
{
int x = 0, f = 0;
char c = getchar();
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ '0');
return f ? -x : x;
}
int min(int a, int b) { return a < b ? a : b; }
int max(int a, int b) { return a > b ? a : b; }
int n, q, d[N], m[N];
int len, block, lef[RT], rig[RT], be[N];
int top, tot[RT][3];
struct range { int G, S; } ran[RT][3][N], fuck[RT], stk[N];
int cmp(range a, range b) { return a.G == b.G ? a.S < b.S : a.G < b.G; }
void doit(int i, int typ)
{
stk[top = 1] = ran[i][typ][1];
for (int j = 2; j <= tot[i][typ]; j++)
{
while (top > 0 && ran[i][typ][j].S >= stk[top].S) top--;
stk[++top] = ran[i][typ][j];
}
tot[i][typ] = top;
for (int j = 1; j <= top; j++) ran[i][typ][j] = stk[j];
}
int getit(int i, int typ, int x0)
{
int low = 1, up = tot[i][typ], mid, res = -1;
while (low <= up)
{
mid = low + up >> 1;
if (ran[i][typ][mid].G < x0 + ran[i][typ][mid].S) low = mid + 1, res = mid;
else up = mid - 1;
}
if (res == -1) return min(ran[i][typ][1].G, x0 + ran[i][typ][1].S);
int w = min(ran[i][typ][res].G, x0 + ran[i][typ][res].S);
if (res < tot[i][typ]) w = max(w, min(ran[i][typ][res + 1].G, x0 + ran[i][typ][res + 1].S));
return w;
}
void init()
{
n = read(), q = read();
len = sqrt(n), block = n / len;
if (n % len) block++;
for (int i = 1; i <= n; i++) d[i] = read();
for (int i = 1; i <= n; i++) m[i] = read(), be[i] = (i - 1) / len + 1;
for (int i = 1; i <= block; i++)
{
lef[i] = (i - 1) * len + 1, rig[i] = min(i * len, n);
for (int j = lef[i]; j <= rig[i]; j++)
{
int now = INF, sum = 0;
for (int k = j; k <= rig[i]; k++)
now = min(now + d[k], m[k]), sum += d[k], ran[i][0][++tot[i][0]].G = now, ran[i][0][tot[i][0]].S = sum;
}
for (int j = lef[i], now = INF, sum = 0; j <= rig[i]; j++)
{
now = min(now + d[j], m[j]), sum += d[j], ran[i][1][++tot[i][1]].G = now, ran[i][1][tot[i][1]].S = sum;
if (j == rig[i]) fuck[i].G = now, fuck[i].S = sum;
}
for (int j = lef[i]; j <= rig[i]; j++)
{
int now = INF, sum = 0;
for (int k = j; k <= rig[i]; k++) now = min(now + d[k], m[k]), sum += d[k];
ran[i][2][++tot[i][2]].G = now, ran[i][2][tot[i][2]].S = sum;
}
sort(ran[i][0] + 1, ran[i][0] + tot[i][0] + 1, cmp);
sort(ran[i][1] + 1, ran[i][1] + tot[i][1] + 1, cmp);
sort(ran[i][2] + 1, ran[i][2] + tot[i][2] + 1, cmp);
doit(i, 0), doit(i, 1), doit(i, 2);
}
}
void solve()
{
while (q--)
{
int l = read(), r = read(), x0 = read(), ans = 0;
int firb = be[l], lasb = be[r];
if (firb == lasb)
{
for (int i = l, sum = x0; i <= r; i++) sum = max(x0, min(sum + d[i], m[i])), ans = max(ans, sum);
printf("%d\n", ans);
continue;
}
for (int i = firb + 1; i <= lasb - 1; i++) ans = max(ans, getit(i, 0, x0));
int y = 0;
for (int i = l, sum = x0; i <= rig[firb]; i++)
{
sum = max(x0, min(sum + d[i], m[i])), ans = max(ans, sum);
if (i == rig[firb]) y = max(y, sum);
}
ans = max(ans, y);
for (int i = firb + 1; i <= lasb - 1; i++)
{
ans = max(ans, getit(i, 1, y));
y = max(min(fuck[i].G, fuck[i].S + y), max(x0, getit(i, 2, x0)));
ans = max(ans, y);
}
for (int i = lef[lasb], sum = y; i <= r; i++) sum = min(sum + d[i], m[i]), ans = max(ans, sum);
for (int i = lef[lasb], sum = x0; i <= r; i++) sum = max(x0, min(sum + d[i], m[i])), ans = max(ans, sum);
printf("%d\n", ans);
}
}
int main()
{
//freopen("park.in", "r", stdin);
//freopen("park.out", "w", stdout);
//freopen("input", "r", stdin);
//freopen("output", "w", stdout);
init();
solve();
fclose(stdin);
fclose(stdout);
return 0;
}