1. 程式人生 > 實用技巧 >[noi.ac 模擬賽8] c(容斥/DP)

[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
*/

總結

應該算是對容斥更深入的理解。