2017 Multi-University Training Contest 5

Rikka系列题目解析
本文解析了Rikka系列的五道算法题目,包括Rikka with Candies的模2卷积解决方法,Rikka with String的AC自动机应用,Rikka with Graph的贪心策略,Rikka with Subset的背包问题转换,以及Rikka with Number的好数计算策略。

1001 Rikka with Candies

题解1:
考虑预处理出所有 kk 的答案,问题相当于一个模 22 意义下的 \text{mod}mod 卷积,即给出数组 A,BA,B,将 A_i \times B_jAi×Bj 累加到 w_{i\ \text{mod}\ j}wi mod j 上。

因为 i\ \text{mod}\ j=i-j\lfloor \frac{i}{j} \rfloori mod j=ijji,因此可以枚举 jj 和 k=\lfloor \frac{i}{j} \rfloork=ji。这时相当与把 AA 中区间 [kj,(k+1)j)[kj,(k+1)j)中的所有数依次加到 ww 的 [0,j)[0,j) 区间上。

因为模 22 意义下的加法可以用异或实现,因此可以用压位来优化,这样时间复杂度为 \sum_{i=1}^n \frac{n}{i} \times (32+\frac{i}{32})i=1nin×(32+32i),这约是 \frac{n^2}{32}32n2

注意因为 bitset 不支持提取区间操作,因此需要手写压位。如果不手写也可以用分块的方法来达到同样的复杂度但是常数比手写 bitset 大很多,可能需要一些常数优化才能通过时限。

题解2:
先将a中出现的所有数字用bitset记录,那么我们只需要考虑出现奇数次的a。下面考虑余数为0的情况,此时可以先对于每一个b预处理出所有的令余数为0时a可能的取值,用bitset记录下来,我们只需考虑被用到奇数次的数。那么求解时,就可以用上边的两个bitset做与运算,在统计1的个数即可。对于余数为k的情况,只需要将记录下来的第二个bitset向左移动k位,就得到了余数为k时需要的取值。当然,要去除掉b<=k的情况,所以只需先都读入后,在按顺序去做即可
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 50000 + 10 ;
bitset<maxn> cnt,cnt2,tmp;
int a[maxn],b[maxn],k[maxn];
struct LX{
	int p;
	int k;
	bool operator < (const LX & b) const{
		return k < b.k;
	}
}x[maxn];
int ans[maxn];
int B[maxn];
int n,m,q;
int main()
{
	int kase;scanf("%d",&kase);
	while(kase--){
		memset(B,0,sizeof(B));
		cnt.reset();
		cnt2.reset();
		tmp.reset();
		scanf("%d%d%d",&n,&m,&q);
		for(int i = 1;i <= n;i++){
			scanf("%d",&a[i]);
			cnt[a[i]] = cnt[a[i]] ^ 1;
		}
		for(int i = 1;i <= m;i++){
			scanf("%d",&b[i]);
			B[b[i]] ^= 1;
		}m = 0;
		for(int i = 0;i <= 50000;i++) if(B[i] == 1) b[++m] = i;
		for(int i = 1;i <= m;i++){
			for(int j = 0;j < maxn;j+=b[i]){
				cnt2[j] = cnt2[j] ^ 1;
			}
		}
		for(int i = 1;i <= q;i++){
			scanf("%d",&x[i].k);x[i].p = i;
		}
		sort(x + 1,x + q + 1);
		int p = 1,last = 0;;
		for(int i = 1;i <= q;i++){
			int j = x[i].k;
			if(i > 1  && x[i].k == x[i - 1].k) {ans[x[i].p] = last;continue;}
			while(b[p] <= j) {
				for(int jj = 0;jj < maxn;jj+=b[p]){
					cnt2[jj] = cnt2[jj] ^ 1;
				}
				p++;
			}
			while(p <= m && b[p] == b[p + 1]) p += 1;
			tmp = cnt2 << j;
			ans[x[i].p] = (tmp & cnt).count() % 2;
			last = ans[x[i].p];
		}
		for(int i = 1;i <= q;i++) {printf("%d\n",ans[i]);}
	}
	return 0;
}


1002 Rikka with String

如果没有反对称串的限制,直接求一个长度为 LL 的 0101 串满足所有给定串都出现过,那么是一个经典的 AC 自动机的问题,状态 f[i][j][S]f[i][j][S] 表示长度为 ii,目前在 AC 自动机的节点 jj 上,已经出现的字符串集合为 SS 的方案数,然后直接转移即可,时间复杂度 O(2^nL\sum |s|)O(2nLs)

然后如果不考虑有串跨越中轴线,那么可以预处理所有正串的 AC 自动机和所有反串(即原串左右翻转)的 AC 自动机,然后从中间向两边 DP,每一次枚举右侧下一个字符是 00 还是 11,那么另一侧一定是另外一个字符。状态 f[i][j][k][S]f[i][j][k][S] 表示长度为 2i2i,目前右半边在正串 AC 自动机的节点 jj 上,左半边的反串在反串 AC 自动机的节点 kk 上,已经出现的字符串集合为 SS 的方案数,然后直接转移,时间复杂度 O(2^nL(\sum |s|)^2)O(2nL(s)2)

现在考虑有串跨越中轴线,可以先爆枚从中间开始左右各 \max|s|-1maxs1 个字符,统计出哪些串以及出现了。对于之后左右扩展出去的字符来说,肯定没有经过的它们的字符串跨越中轴线,因此可以以爆枚的结果为 DP 的初始值,从第 \max|s|maxs 个字符开始 DP。


1006 Rikka with Graph

考虑贪心地一条一条边添加进去。

当 m \leq n-1mn1 时,我们需要最小化距离为 nn 的点对数,所以肯定是连出一个大小为 m+1m+1 的联通块,剩下的点都是孤立点。在这个联通块中,为了最小化内部的距离和,肯定是连成一个菊花的形状,即一个点和剩下所有点直接相邻。

当 m > n-1m>n1 时,肯定先用最开始 n-1n1 条边连成一个菊花,这时任意两点之间距离的最大值是 22。因此剩下的每一条边唯一的作用就是将一对点的距离缩减为 11

这样我们就能知道了最终图的形状了,稍加计算就能得到答案。要注意 mm 有可能大于 \frac{n(n-1)}{2}2n(n1)


1008 Rikka with Subset

发现每一个数都只能由比他更小的数组成,所以可以由B[0]直接求出0的个数,再求出1的个,2的个数。。。。在这个过程中,为了求出n的个数,只需求出0~n-1组成了多少个n即可。这就相当于一个背包问题,每次求出新数时当作新物品计算即可


1009 Rikka with Number

首先转化成计算小于等于 NN 的好数有多少个。因为 n^n<(n+1)^nnn<(n+1)n,而对于 nn 进制下的任何一个好数 KK,都有 n^{n-1}<K<n^nnn1<K<nn,所以每一个进制下好数的大小区间是不相交的。

不难发现 dd 进制下好数的个数为 d!-(d-1)!d!(d1)!,因此我们只需要计算在临界的 dd 进制下,好数的个数就可以了。关于临界的 dd,可以用对数估计位数得到。

求 dd 进制下小于等于 NN 的好数个数,先讲 NN 转化成 dd 进制,然后做一个类似康托展开的过程就可以了,因为数据范围很小,所以每一步操作都可以暴力进行。

时间复杂度 O(|R|^2)O(R2)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值