【NOIP2018模拟10.29】总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/er111er/article/details/83586673

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 个点的二分图,对于每个不好惹的同学 ( x , y ) (x,y) 将左边的 x x 连向右边的 y y ,设这个图的最大独立集左边共选 a a 个点,右边共选 b b 个点,答案就是 n ( n a ) + n ( n b ) = n ( 2 n ( 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 &amp; b + a b a \&amp; b+a | b
我好像在初赛见过这东西。
那道题是让你求 0 a , b 31 0\le a,b \le 31 时方程 a b = ( a &amp; b ) ( a b ) ab=(a \&amp; b)(a | b) 的解数。
然鹅两题并没有什么相似之处,重要的是这个性质:
a &amp; b + a b = a + b a \&amp; b+a | b=a+b
证明比较easy。
首先,两个二进制数相加时,它俩互换一下相同位上的数,结果是一样的。
如果 a , b a,b 某一位上都是 1 1 ,那么 a &amp; b , a b a \&amp; b,a | b 的那一位都是 1 1 。如果如果 a , b a,b 有一位上 1 1 ,那么 a &amp; b a \&amp; b 那一位是 0 0 a b a | b 那一位是 1 1 ,互换一下,结果不变。如果 a , b a,b 某一位都是 0 0 a &amp; b , a b a \&amp; b,a | b 的那一位都是 0 0 ,相加都是0。
因此 a &amp; b + a b = a + b a \&amp; b+a | b=a+b

题目就变成给你 n n 个数,每次可以选俩相邻的数 a , b a,b 合在一起变成 ( a + b ) / 2 (a+b)/2 ,问你最终可能留下哪些数。
显然区间dp:设 f i , j , k = 1 f_{i,j,k}=1 表示区间 [ i , j ] [i,j] 可能出现 k k f i , j , k = 0 f_{i,j,k}=0 表示区间 [ i , j ] [i,j] 不可能出现 k k 。一开始我只从区间两端转移,一拍就炸了。正确的转移是枚举 i t j 1 i \le t \le j-1 ,然后再枚举区间 [ i , t ] [i,t] 留下的哪个数 g g ,那么只要( g + g+ 区间 [ t + 1 , j ] [t+1,j] 留下的数) = 2 k =2k 2 k + 1 2k+1 f i , j , k = 1 f_{i,j,k}=1
最后枚举哪些 i i 满足 f 1 , n , i = 1 f_{1,n,i}=1 就行了。

跑满的话时间复杂度是 O ( 64 n 3 ) O(64n^3) ,大概 2 2 亿,在 f i , j , k = 1 f_{i,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

来自俄罗斯的分块!

这又是一道亦可赛艇的分块题,然鹅考试的时候连 O ( n q ) O(nq) 做法都想不到, O ( n q ) O(nq) 做法能水到 90 p t s 90pts …。

先不管询问的限制,设 F ( l , r , x 0 ) F(l,r,x_0) 表示以 x 0 x_0 为初始愉悦度,从 l l 走到 r r 最终的愉悦度。
这玩意是关于 x 0 x_0 单调的,也就是说有第一个性质:

a b a\le b ,则 F ( l , r , a ) F ( l , r , b ) F(l,r,a)\le F(l,r,b)

这个性质的证明显然。

光有这性质还不够,设 G ( l , r ) = F ( l , r , ) G(l,r)=F(l,r,\infty) S ( l , r ) = i = l r d i S(l,r)=\sum_{i=l}^{r}d_i

则有 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))

G ( l , r ) &lt; x 0 + S ( l , r ) G(l,r)&lt;x_0+S(l,r) ,则必然会在某个点 i i 答案与 l i l_i m i n min ,这样答案就等同于以无穷大的愉悦度出发。因为 F ( l , r , x 0 ) F(l,r,x_0) G ( l , r ) G(l,r) 分别会在某个位置愉悦度变成 l i l_i ,所以最终答案是一样的。反之若 x 0 + S ( l , r ) &lt; G ( l , r ) x_0+S(l,r)&lt;G(l,r) ,则最多只能获得 x 0 + S ( l , r ) x_0+S(l,r) 的愉悦度。由此得到上面的性质。

然后考虑把数列分块。

对于一个询问 [ l , r ] [l,r] ,可能的答案有四种:
1.中间构成整块的区间中的一个区间。
2.左边不构成整块的区间中的一个区间。
3.右边不构成整块的区间中的一个区间。
4.跨过多个区间的一个区间。

先考虑1:
对于同一块中的两个区间 [ l 1 , r 1 ] [l_1,r_1] [ l 2 , r 2 ] [l_2,r_2] ,若 G ( l 1 , r 1 ) G ( l 2 , r 2 ) G(l_1,r_1)\le G(l_2,r_2) S ( l 1 , r 1 ) S ( l 2 , r 2 ) S(l_1,r_1)\le S(l_2,r_2) ,那么对于所有的 x 0 x_0 都有 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) ,也就是 [ l 1 , r 1 ] [l_1,r_1] 永远轮不上它, [ l 2 , r 2 ] [l_2,r_2] 比它更优。这个可以由第二个性质得到。于是对于每个块,处理出其所有的子区间,由于块的长度是 O ( n ) O(\sqrt n) ,子区间个数是 O ( n ) O(n) 的,我们只需要做一遍类似单调栈的过程就能去掉那些没用的区间。得到一个 G G 值严格递增, S S 值严格递减的区间序列。每个块都如此做,复杂度 O ( n n ) O(n\sqrt n)
对于一个询问 x 0 x_0 ,那么 G G x 0 + S x_0+S 都具有单调性,因此可以通过二分找到最大的 G G 使 G &lt; x 0 + S G&lt;x_0+S ,设它在序列区间里是第 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})))
答案比较显然,证明略。
块的数量是 O ( n ) O(\sqrt n) ,每次二分复杂度 O ( l o g n ) O(logn) ,总共复杂度就是 O ( n l o g n ) O(\sqrt n · logn)

考虑2,3:
采用 O ( n q ) O(nq) 的算法,扫一下这个区间,每次都把答案加上 d i d_i 并和 l i l_i m i n min ,如果这样以后得到的答案小于 x 0 x_0 ,那就从 x 0 x_0 重新走。小块的长度是 O ( n ) O(\sqrt n) ,两个小块都做一遍复杂度是 O ( n ) O(\sqrt n)

考虑4:
我们只用考虑右端点在当前块的情况。
把答案区间拆成两个区间:这个块左端点以前紧靠着左端点的一个区间,这个块的某个前缀。
设前者答案是 y y ,那么我们可以将 y y 看作走这个块的某个前缀的初始愉悦度,用类似1的处理方法求出所有前缀,二分求出答案。
求完右端点在该块的答案之后,我们还要计算这个块应该给到下一块的 y y 。我们有三个选择,一个是把当前块全部走过,一个是以 x 0 x_0 为初始愉悦度走过该块的某个后缀,还有一个就是直接从 x 0 x_0 开始。由性质1的单调性我们可知, y y 越大答案就会越大,我们只需要在三者中取最大的一个即可。
这里有两次二分,总共复杂度是 O ( n l o g n ) O(\sqrt n · logn)

所以总的复杂度是: O ( n n + q n l o g n ) O(n\sqrt n+q\sqrt n · 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;
}

猜你喜欢

转载自blog.csdn.net/er111er/article/details/83586673
今日推荐