Loading

AtCoder Regular Contest 068

C 简单,D 有点意思,但没啥用,不写。

F 有点好玩,想了一个小时,看了一上午题解,弄明白了。

E - Snuke Line

\(n\) 个物品,第 \(i\) 个物品在 \([l_i,r_i]\) 间。

你初始在第 0 格,要走到第 \(m\) 格,求对于 \(1\sim m\) 每一种步长,走到的格子里的物品种类之和。

\(1\le n\le 10^5,1\le m\le 3\times 10^5\)

很有意思的题目。

首先有一种数论分块的做法,这里略过,可以参考洛谷题解。

这道题树状数组的解法很有参考意义,这里简要说一下:

首先,步长 \(\le r_i-l_i+1\) 的必定含第 \(i\) 种物品,原因显然,而步长 \(\gt r_i-l_i+1\) 只会经过 \([l_i,r_i]\) 至多一次。

离线下来,对于步长 \(x\),将所有 \(r_i-l_i+1\le x\)\([l_i,r_i]\) 区间加,然后调和级数单点查询即可。这样显然不会漏算或多算。最后再加上区间长度大于 \(x\) 的即可。

时间复杂度 \(\mathcal O(n\ln n\log n)\)

Code

// Problem: E - Snuke Line
// Contest: AtCoder - AtCoder Regular Contest 068
// URL: https://atcoder.jp/contests/arc068/tasks/arc068_c
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define fir first
#define sec second
using pii = std::pair<int,int>;

const int maxn = 3e5 + 5;
int c[maxn],n,m;
pii a[maxn];

int lowbit(int x) {
	return x & -x;
}

void add(int x,int y) {
	for(;x <= m;x += lowbit(x))c[x] += y;
	return ;
}

int query(int x) {
	int ans = 0;
	for(;x;x -= lowbit(x))ans += c[x];
	return ans;
}

int main() {
	scanf("%d %d",&n,&m);
	for(int i = 1;i <= n;++ i)
		scanf("%d %d",&a[i].fir,&a[i].sec);
	std::sort(a + 1 , a + 1 + n , [&](const pii& p,const pii& q) {
		return p.sec - p.fir < q.sec - q.fir;
	});
	
	for(int i = 1,j = 1;i <= m;++ i) {
		for(;j <= n&&a[j].sec - a[j].fir + 1 < i;++ j) {
			add(a[j].fir , 1);
			add(a[j].sec + 1 , -1);
		}
		int ans = 0;
		for(int k = i;k <= m;k += i)
			ans += query(k);
		printf("%d\n",ans + n - j + 1);
	}
}

F - Solitaire

给定一个双端队列,将 \(1\sim n\) 从头或尾插入队列。然后再依次从队头或队尾取出元素,形成一个序列,问 1 在序列的第 \(k\) 个位置的序列数量。

\(1\le k\le n\le 2000\)

很神奇的一道题。给出一种 \(\mathcal O(n^2)\) 的算法,似乎有基于 Dilworth 定理的 \(\mathcal O(n)\) 算法,不过我不会,就算了。

首先,根据 ARC F 这类计数题的套路,一开始不要纠结序列的具体形态,尝试思考序列的合法性,找一找序列的性质。

发现这个双端队列中的元素必然呈单谷状,设 \(Q_j=1\),则 \(Q_1\gt Q_2\gt\dots Q_j\lt Q_{j+1}\lt \dots\lt Q_n\)

要让 1 在第 \(k\) 位,就要把 \(Q_{1\sim j-1},Q_{n-(k-j)+1\sim n}\) 先取出来,然后把 \(Q_j\) 取出来。

那么剩下的 \(n-k\) 个数所能构成的序列数为 \(2^{n-k-1}\),我们要关注的是前面一段的数量。

考虑一个长为 \(k\) 的序列怎样才能符合我们的要求:根据双端队列的单谷性质,这个删除序列一定可以划分为两个互不相交的单调递减子序列,分别记为 \(A,B\),并且 \(A,B\) 其中之一的最小值要大于 \(\max\limits_{i\in [j+1,n-(k-j)]}Q_i\)

因为 \(A,B\) 有无序对整个序列无影响,不妨设 \(A\) 的最小值小于 \(B\) 的最小值,且 \(B\) 要满足上述性质(最小值大于那一串式子)

\(f(i,j)\) 表示序列中已有 \(i\) 个数,最小值为 \(j\) 时的方案数。

\(i-1\to i\) 不好推,我们考虑从 \(i\to i+1\) 这样转移状态。分情况讨论:

  • 新添的数放入 \(A\) 中:设这个数为 \(p\),则 \(p\lt j\)\(f(i,j)\) 转移到 \(f(i+1,p)\)

  • 新添的数放入 \(B\) 中:根据性质,放入的这个数必须是未选数的最大值,不超过 \(n-i\),所以若 \(n-i\lt j\) 就不满足 \(A\) 的最小值比 \(B\) 小的原则,故当 \(j\le n-i\) 时,\(f(i,j)\) 可以转移至 \(f(i+1,j)\)

最后统计下即可。

Code

// Problem: F - Solitaire
// Contest: AtCoder - AtCoder Regular Contest 068
// URL: https://atcoder.jp/contests/arc068/tasks/arc068_d
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using i64 = long long;

const i64 mod = 1e9 + 7;
const int maxn = 2005;
int n,k;
i64 f[maxn][maxn];

i64 power(i64 x,i64 y) {
	i64 ans = 1;
	for(;y;y >>= 1) {
		if(y & 1)(ans *= x) %= mod;
		(x *= x) %= mod;
	}
	return ans;
}

int main() {
	scanf("%d %d",&n,&k);
	if(n <= 2)return printf("1"),0;
	if(k == 1)return printf("%lld\n",power(2 , n - k - 1)),0;
	for(int i = 1;i <= n;++ i)f[1][i] = 1;
	for(int i = 1;i < k - 1;++ i) {
		f[i + 1][n - i + 1] = f[i][n - i + 1];
		for(int j = n - i;j >= 2;-- j)
			(f[i + 1][j] += f[i + 1][j + 1] + f[i][j]) %= mod;
	}
	i64 ans = 0;
	for(int i = 2;i <= n - k + 2;++ i)
		(ans += f[k - 1][i]) %= mod;
	printf("%lld",ans * power(2 , std::max(0 , n - k - 1)) % mod);
	return 0;
}
posted @ 2022-11-04 16:08  Skylakes  阅读(34)  评论(0编辑  收藏  举报