【NOIP2018模拟10.23】 总结

做题策略的总结

这场比赛吸取了昨天的教训,上来直接把暴力打完了,忍住没去想正解。预估分数是120,但是由于我没有梦想,打了一个特判点却开的是暴力的数据范围,于是少了10分。还有打T3暴力的时候太颓废,没有打k=1的点,白白少了10分。
最终分数110,我还是太菜了。

T1

T1
这是一道非常NOIP的差分。
数据范围能够启发人类智慧(@cyc):
Constraints
O ( n 2 ) O(n^2) O(n2)的做法比较简单,可以得到 30 p t s 30pts 30pts的好成绩。
如果动动脑子发现 k = 1 k=1 k=1也很简单,可以得到 40 p t s 40pts 40pts的好成绩。
但就是这个 k = 1 k=1 k=1可以启发你想出正解。

假如我们的操作变成 [ l , r ] [l,r] [l,r]加上1,只需要差分一下就可以。
如果操作变成对于每个 l ≤ i ≤ r l \le i \le r lir a i a_i ai加上 i − l i-l il,相当于把区间里的一段对应加上一个等差数列,我们可以将原数列 a a a的差分数列 d d d再差分一次得到数列 d 1 d1 d1。在 d 1 d1 d1打标记以后求前缀和得到 d d d,再根据 d d d得到 a a a

上面的分别是加上 C i − l 0 C_{i-l}^{0} Cil0和加上 C i − l + 1 1 C_{i-l+1}^{1} Cil+11的情况。

那么如果加上的是 C i − l + 2 2 C_{i-l+2}^{2} Cil+22
我们可以把 d 1 d1 d1的差分数列 d 2 d2 d2搞出来,在 d 2 d2 d2上修改,然后得到 d 1 d1 d1,由 d 1 d1 d1得到 d d d,再由 d d d得到 a a a

由此我们不难发现,如果要应对一般情况,我们只需要维护一个 k k k阶的差分数组,最后从 d [ k ] d[k] d[k] d [ 0 ] d[0] d[0]还原上去。

关于什么是 k k k阶差分数组,将 a a a差分得到 d 1 d1 d1 d 1 d1 d1就是 1 1 1阶差分数组,将 d 2 d2 d2差分得到 d 2 d2 d2 d 2 d2 d2就是 2 2 2阶差分数组,以此类推。

写的很丑压线过去的Code:

#include <cstdio>
#include <cstring>
#include <cstdlib>

typedef long long ll;
const int N = 5e5 + 27, K = 27;
const ll P = 1e9 + 7;
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 n, m;
ll d[N][K], C[N][K], sum[K][N];

int main()
{
	freopen("sequence.in", "r", stdin);
	freopen("sequence.out", "w", stdout);

	n = read(), m = read();
	C[0][0] = 1;
	for (int i = 1; i <= n + 20; i++)
	{
		C[i][0] = 1;
		for (int j = 1; j <= 20; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % P;
	}
	for (int k = 0; k <= 20; k++)
		for (int i = 1; i <= n + 20; i++)
			sum[k][i] = (sum[k][i - 1] + C[i][k]) % P;
	for (int i = 1, l, r, k; i <= m; i++)
	{
		l = read(), r = read(), k = read();
		d[l][k + 1] = (d[l][k + 1] + 1) % P, d[r + 1][k + 1] = (d[r + 1][k + 1] - 1 + P) % P;
		for (int j = k - 1; j >= 0; j--)
		{
			int tmp = k - j - 1;
			d[r + 1][j + 1] = (d[r + 1][j + 1] - (sum[tmp][r - l + tmp] - sum[tmp][tmp < 1 ? 0 : tmp - 1] + (tmp < 1 ? 1 : 0) + P) % P + P) % P;
		}
	}
	for (int k = 20; k >= 0; k--)
	{
		ll sum = 0;
		for (int i = 1; i <= n; i++) sum = (sum + d[i][k + 1]) % P, d[i][k] = (d[i][k] + sum) % P;
	}
	for (int i = 1; i <= n; i++) printf("%lld\n", d[i][0]);

	fclose(stdin);
	fclose(stdout);
	return 0;
}

T2:
T2
数据范围决定了这题是NOIP难度(如果 n ≤ 1 0 5 n \le 10^5 n105还算个p的NOIP啊)。
Constraints
这题是很经典的递推了,之前听炜哥讲过但并没有认真去想,这次吃亏了。

n ≤ 5 n \le 5 n5暴力枚举每一条边选不选,得到了 30 p t s 30pts 30pts的good grades.
然而没打 k = 1 k=1 k=1,少 10 p t s 10pts 10pts

首先解决一个问题:求有 n n n个带标号的点的无向连通图数目。
g i g_i gi表示有 i i i个带标号的点的无向连通图数目。
我们考虑求出不连通的个数,然后用总数去减。
g i = 2 C i 2 − ∑ j = 1 i − 1 2 C i − j 2 ∗ C i − 1 j − 1 ∗ g j g_i=2^{C_{i}^{2}}-\sum_{j=1}^{i-1}2^{C_{i-j}^{2}}*C_{i-1}^{j-1}*g_j gi=2Ci2j=1i12Cij2Ci1j1gj
后面那一串的意思是,我选出 j j j个点成为一个新的连通块,使它与另外 i − j i-j ij个点断开。然后选一个点固定它,在另外 i − 1 i-1 i1个点里选 j − 1 j-1 j1个陪它组成大小为 j j j的连通块,上面的递推式就得出来了。
然后再考虑答案怎么求,我们设 f i f_i fi表示前 i i i个点组成的无向图,最大连通块大小 ≤ k \le k k的方案数,类似于上面的方法,可以得到:
f i = ∑ j = 1 m i n ( i , k ) f i − j ∗ C i − 1 j − 1 ∗ g j f_i=\sum_{j=1}^{min(i,k)}f_{i-j}*C_{i-1}^{j-1}*g_j fi=j=1min(i,k)fijCi1j1gj
我们用 ≤ k \le k k的方案数减去 ≤ k − 1 \le k - 1 k1的方案数即可得到刚好等于 k k k的方案数。

很丑的Code:

#include <cstdio>
#include <cstring>
#include <cstdlib>

typedef long long ll;
const int N = 2e3 + 7;
const ll P = 998244353;

int n, k;
ll f[N], g[N], pow[N * N], C[N][N];

ll doit(int a)
{
	memset(f, 0, sizeof(f));
	f[0] = 1;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= i && j <= a; j++)
			f[i] = (f[i] + f[i - j] * C[i - 1][j - 1] % P * g[j] % P) % P;
	return f[n];
}

int main()
{
	freopen("bomb.in", "r", stdin);
	freopen("bomb.out", "w", stdout);

	scanf("%d%d", &n, &k);
	pow[0] = 1; for (int i = 1; i <= (n * (n - 1) / 2); i++) pow[i] = pow[i - 1] * 2 % P;
	C[0][0] = 1;
	for (int i = 1; i <= n; i++)
	{
		C[i][0] = 1;
		for (int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % P;
	}
	g[1] = 1;
	for (int i = 2; i <= n; i++)
	{
		g[i] = pow[i * (i - 1) / 2];
		ll sum = 0;
		for (int j = 1; j <= i - 1; j++) sum = (sum + pow[(i - j) * (i - j - 1) / 2] * C[i - 1][j - 1] % P * g[j] % P) % P;
		g[i] = (g[i] - sum + P) % P;
	}
	printf("%lld\n", (doit(k) - doit(k - 1) + P) % P);

	fclose(stdin);
	fclose(stdout);
	return 0;
}

T3
T3
数据范围启发人类智慧:
Constraints

n ≤ 2000 n \le 2000 n2000无脑暴力。
整体翻转的数据打主席树就可以get到 40 p t s 40pts 40pts 的好成绩。

简单的分块算法。
对于整个数列分块,每一块维护一个桶记录块内数的出现次数,并维护一个双端队列,存放当前块内元素。
每次修改,先把最右边的块的元素放到最左边的块对应的位置,由于每一块维护的元素最多只有 n \sqrt{n} n 个,这个复杂度就是 O ( n ) O(\sqrt{n}) O(n ),再把中间的块每一块最后一个元素放到下一块开头,最多有 n \sqrt{n} n 个块,这个复杂度也是 O ( n ) O(\sqrt{n}) O(n )
每次查询,两端多余的部分暴力统计,中间的块用桶记录,这样复杂度也是 O ( n ) O(\sqrt{n}) O(n ),于是总共复杂度就是 O ( q n ) O(q\sqrt{n}) O(qn ),评测时吸氧,可以在 1 s 1s 1s内通过。

这数据结构题码的就是爽。

写得一般般的Code:

#include <queue>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;

const int N = 1e5 + 7, RT = 320;
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 n, m, a[N];

int len, block, be[N];
int lef[RT], rig[RT], buc[RT][N];
deque<int> lis[RT];

void init()
{
	for (int i = 1; i <= n; i++) a[i] = read();
	len = sqrt(n), block = n / len;
	if (n % len) block++;
	for (int i = 1; i <= block; i++) lef[i] = (i - 1) * len + 1, rig[i] = min(i * len, n);
	for (int i = 1; i <= n; i++)
	{
		be[i] = (i - 1) / len + 1;
		buc[be[i]][a[i]]++, lis[be[i]].push_back(a[i]);
	}
}

void solve()
{
	while (m--)
	{
		int opt = read(), l = read(), r = read();
		if (opt == 1)
		{
			int firb = be[l], lasb = be[r];
			int tmp = lis[lasb].at(r - lef[be[r]]);
			lis[lasb].erase(lis[lasb].begin() + r - lef[be[r]]);
			buc[lasb][tmp]--;
			lis[firb].insert(lis[firb].begin() + l - lef[be[l]], tmp);
			buc[firb][tmp]++;
			for (int i = firb; i <= lasb - 1; i++)
			{
				int tmp = lis[i].back();
				lis[i].pop_back(), buc[i][tmp]--;
				lis[i + 1].push_front(tmp), buc[i + 1][tmp]++;
			}
		}
		else
		{
			int firb = be[l], lasb = be[r], cnt = 0, k = read();
			if (firb == lasb)
			{
				int p = lef[firb];
				deque<int>::iterator it = lis[firb].begin();
				while (p < l) it++, p++;
				while (p <= r) cnt += (*it == k), it++, p++;
			}
			else
			{
				int p = rig[firb];
				deque<int>::iterator it = lis[firb].end(); it--;
				while (p >= l) cnt += (*it == k), it--, p--;
				p = lef[lasb], it = lis[lasb].begin();
				while (p <= r) cnt += (*it == k), it++, p++;
				for (int i = firb + 1; i <= lasb - 1; i++) cnt += buc[i][k];
			}
			printf("%d\n", cnt);
		}
	}
}

int main()
{
	freopen("queue.in", "r", stdin);
	freopen("queue.out", "w", stdout);
	
	n = read(), m = read();
	if (!n) return 0;
	init();
	solve();
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

夜深人静了,只剩我在机房写博客。成功的道路总是孤独的, 明天继续加油吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值