[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_{Ssubseteq K,|S|=i} lfloor frac{n}{mathrm{lcm}(S)} floor - sum_{i < j le n} dbinom{j}{i} f[j] ]

对于前面这个式子 (sumlimits_{Ssubseteq K,|S|=i} lfloor frac{n}{mathrm{lcm}(S)} floor)

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

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

时间复杂度 (O(Qlog 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
",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
*/

总结

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

原文地址:https://www.cnblogs.com/BaseAI/p/14057031.html