[noi.ac 模拟赛8] c(容斥/DP)

题目

题目描述

你是一位精明的数学家,擅长用计算机来解决各类小学奥数题。

这一天,你想起来了著名的 "开关灯问题", 它曾经经常被拿来为难小朋友们。 于是你决定完成一个程序来彻底解决这个问题。

"开关灯问题" 在小学奥数书上的描述是这样的:

一个走廊里有 \(100\) 盏按顺序排好的灯, 它们分别拥有 \(1∼100\) 的编号,最开始时都是关上的。 小明同学非常无聊,他首先按下了所有编号为 \(2\) 的倍数的灯的开关, 接着他又按下了所有编号为 \(3\) 的倍数的灯的开关, 问最后有多少灯是打开的。

因为你希望最终完全解决这类问题,因此你将题目一般化:

现在有三种灯的开关:第一种开关是熔断型的, 即只要第一次使用开关后,开关将保持打开状态不变;第二种 开关是普通型的,即使用奇数次开关时,灯会打开,使用偶数 次时,灯又会关闭;第三种开关是记忆型的,使用次数为三的倍数时,灯会关闭, 否则灯打开。

一个走廊里有 \(n\) 盏按顺序排好的灯, 它们使用的都是第 \(type\) 种开关(\(type=1、2\)\(3\))。 最开始时,\(1∼n\)号灯都是关上的,开关也从未使用过。 小明同学非常无聊,他进行了 \(d\) 次操作,每一次操作形如: "将编号为 \(k_i\) 的倍数的灯的开关按下。" 问经过这 \(d\) 次操作,最终有多少盏灯是打开的。

你完成的程序需要实现 \(Q\) 次任务, 每一次任务会给出 \(n,type,d,k_i\)

数据范围

\(Q \le 1000,n \le 10^8,1 \le k_i \le n,d \le 12\)

题解

\(K=\{k_i\}\),即题目给出的这个集合。

\(f[i]\) 表示恰好拉了 \(i\) 次的灯的数量,则有:

\[f[i]=\sum_{S\subseteq K,|S|=i} \lfloor \frac{n}{\mathrm{lcm}(S)} \rfloor - \sum_{i < j \le n} \dbinom{j}{i} f[j] \]

对于前面这个式子 \(\sum\limits_{S\subseteq K,|S|=i} \lfloor \frac{n}{\mathrm{lcm}(S)} \rfloor\)

  • 若一个点 \(p\) 恰好被拉了 \(i\) 次,则只会被计算一次。

  • 若一个点 \(p\) 恰好被拉了 \(j(j>i)\) 次,则会被多计算 \(\dbinom{j}{i}\) 次。所以后面减去。

时间复杂度 \(O(Q\log n \ 2^d)\)

代码

Talk is cheap.Show me the code.

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
const int N = 20;
int n,d,type;
int k[N],f[N];
int C[N][N];
int gcd(int a,int b) {
	return (b==0 ? a : gcd(b,a%b));
}
int lcm(int a,int b) {
	return a*b/gcd(a,b);
}
void Init() {
	for(int i=0;i<N;++i) C[i][0] = 1;
	for(int i=1;i<N;++i) {
		for(int j=1;j<=i;++j) {
			C[i][j] = C[i-1][j] + C[i-1][j-1];
		}
	}
}
void work() {
	memset(f, 0, sizeof(f));
	n = read(), type = read(), d = read();
	for(int i=0;i<d;++i) k[i] = read();
	int up = (1<<d)-1;
	for(int i=1;i<=up;++i) {
		int S = 1, sz = 0;
		for(int j=0;j<d;++j) {
			if(i&(1<<j)) {
				++sz;
				S = lcm(S,k[j]);
			}
		}
		f[sz] += n/S;
	}
	for(int i=d;i>=1;--i) {
		for(int j=i+1;j<=d;++j) {
			f[i] -= C[j][i]*f[j];
		}
	}
	int ans = 0;
	if(type == 1) {
		for(int i=1;i<=d;++i) ans += f[i];
	} else if(type == 2) {
		for(int i=1;i<=d;++i) {
			if(i&1) ans += f[i];
		}
	} else {
		for(int i=1;i<=d;++i) {
			if(!(i%3==0)) ans += f[i];
		}
	}
	printf("%lld\n",ans);
}
signed main()
{
	Init();
	int Q = read();
	while(Q--) work();
    return 0;
}
/*
3
6 1 2
2 3
6 2 2
2 3
6 3 3
1 2 3

4
3
5
*/

总结

应该算是对容斥更深入的理解。

posted @ 2020-11-29 17:31  基地AI  阅读(134)  评论(0编辑  收藏  举报