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且同一个点不会重复出现
想不到的水题。。。。
当天状态很差,看了这题没有任何想法。
事实上这是非常经典的二分图模型。
考虑左右各
个点的二分图,对于每个不好惹的同学
将左边的
连向右边的
,设这个图的最大独立集左边共选
个点,右边共选
个点,答案就是
。
于是只需求出这个图的最大独立集即可,然鹅我不会匈牙利,我选择网络流。
点数边数什么的多开一点,不然可能随机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
(良心水题)
?
我好像在初赛见过这东西。
那道题是让你求
时方程
的解数。
然鹅两题并没有什么相似之处,重要的是这个性质:
证明比较easy。
首先,两个二进制数相加时,它俩互换一下相同位上的数,结果是一样的。
如果
某一位上都是
,那么
的那一位都是
。如果如果
有一位上
,那么
那一位是
,
那一位是
,互换一下,结果不变。如果
某一位都是
,
的那一位都是
,相加都是0。
因此
。
题目就变成给你
个数,每次可以选俩相邻的数
合在一起变成
,问你最终可能留下哪些数。
显然区间dp:设
表示区间
可能出现
,
表示区间
不可能出现
。一开始我只从区间两端转移,一拍就炸了。正确的转移是枚举
,然后再枚举区间
留下的哪个数
,那么只要(
区间
留下的数)
或
,
。
最后枚举哪些
满足
就行了。
跑满的话时间复杂度是 ,大概 亿,在 时就不再转移,可以比较快跑过去。
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
对于全部数据 。
来自俄罗斯的分块!
这又是一道亦可赛艇的分块题,然鹅考试的时候连 做法都想不到, 做法能水到 …。
先不管询问的限制,设
表示以
为初始愉悦度,从
走到
最终的愉悦度。
这玩意是关于
单调的,也就是说有第一个性质:
若 ,则 。
这个性质的证明显然。
光有这性质还不够,设 , 。
则有 。
若 ,则必然会在某个点 答案与 取 ,这样答案就等同于以无穷大的愉悦度出发。因为 和 分别会在某个位置愉悦度变成 ,所以最终答案是一样的。反之若 ,则最多只能获得 的愉悦度。由此得到上面的性质。
然后考虑把数列分块。
对于一个询问
,可能的答案有四种:
1.中间构成整块的区间中的一个区间。
2.左边不构成整块的区间中的一个区间。
3.右边不构成整块的区间中的一个区间。
4.跨过多个区间的一个区间。
先考虑1:
对于同一块中的两个区间
和
,若
且
,那么对于所有的
都有
,也就是
永远轮不上它,
比它更优。这个可以由第二个性质得到。于是对于每个块,处理出其所有的子区间,由于块的长度是
,子区间个数是
的,我们只需要做一遍类似单调栈的过程就能去掉那些没用的区间。得到一个
值严格递增,
值严格递减的区间序列。每个块都如此做,复杂度
。
对于一个询问
,那么
和
都具有单调性,因此可以通过二分找到最大的
使
,设它在序列区间里是第
个,那么答案就是
。
答案比较显然,证明略。
块的数量是
,每次二分复杂度
,总共复杂度就是
。
考虑2,3:
采用
的算法,扫一下这个区间,每次都把答案加上
并和
取
,如果这样以后得到的答案小于
,那就从
重新走。小块的长度是
,两个小块都做一遍复杂度是
。
考虑4:
我们只用考虑右端点在当前块的情况。
把答案区间拆成两个区间:这个块左端点以前紧靠着左端点的一个区间,这个块的某个前缀。
设前者答案是
,那么我们可以将
看作走这个块的某个前缀的初始愉悦度,用类似1的处理方法求出所有前缀,二分求出答案。
求完右端点在该块的答案之后,我们还要计算这个块应该给到下一块的
。我们有三个选择,一个是把当前块全部走过,一个是以
为初始愉悦度走过该块的某个后缀,还有一个就是直接从
开始。由性质1的单调性我们可知,
越大答案就会越大,我们只需要在三者中取最大的一个即可。
这里有两次二分,总共复杂度是
。
所以总的复杂度是: ,常数大可能过不了。
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;
}