1. 程式人生 > >TLE Time Limit Exceeded 高維字首和

TLE Time Limit Exceeded 高維字首和

題意:給出長度為n的序列c,求非負整數序列a,滿足a<2^m,並且有a[i]&a[i+1]=0,對於每個a[i],要保證a[i]不是c[i]的倍數,求這樣的a[i]序列的個數

思路:dp[i][j]表示長度為i以j結尾的序列的個數,可以第一個條件和第三個條件都好處理,關鍵是第二個,可以發現dp[i][j] = ∑(dp[i - 1][k] * (k & j == 0)), 那麼可以轉化為~j & k == k,即k為~j的子集,也就是說dp[i][j] = dp[i - 1][~j] = dp[i - 1][((1 << m) - 1) ^ j]. 所以每次我們算完

dp[i][1..n]後,要對每個dp[i][j]求j的所有子集的和,留作下一次轉移用。

dalao思路 + 精簡程式碼:點選開啟連結

程式碼:

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int,int> P;
const int MAXN = 100010;
const int mod = 1e9;
ll dp[MAXN], sum[MAXN];
int c[55];
int main()
{
	int T, n, m;
	cin >> T;
	while(T--)
	{
		scanf("%d %d", &n, &m);
		for(int i = 1; i <= n; i++)
		scanf("%d", c + i);
		int up = 1 << m;
		for(int i = 0; i < up; i++)
		{
			if(i % c[1])
			dp[i] = sum[i] = 1;
			else
			dp[i] = sum[i] = 0;	
		}
		for(int j = 0; j < m; j++)
		for(int k = 0; k < up; k++)
		if(k & 1 << j)
		sum[k] = (sum[k] + sum[k ^ (1 << j)]) % mod;//求k的子集的和
		for(int i = 2; i <= n; i++)
		{
			for(int j = 0; j < up; j++)
			{
				if(j % c[i])
				dp[j] = sum[(up - 1) ^ j];
				else
				dp[j] = 0;
			}
			for(int j = 0; j < up; j++)
			sum[j] = dp[j];
			for(int j = 0; j < m; j++)
			for(int k = 0; k < up; k++)
			if(k & 1 << j)
			sum[k] = (sum[k] + sum[k ^ (1 << j)]) % mod;
		}
		ll ans = 0;
		for(int i = 0; i < up; i++)
		ans = (ans + dp[i]) % mod;
		cout << ans << endl;
	}	
 	return 0;
}

第一次做高維字首和的題,個人理解:高維字首和可以解決狀壓表示的一類集合之間的字首和問題,例如求某個集合所有子集對應的值的和,或者某個集合的所有超集和。

求超集和:

for(int i = 0; i < m; i++)
for(int j = 0; j < (1 << m); j++)
if(!(j & 1 << i))
dp[i] += dp[j | (1 << i)];

求子集和:
for(int j = 0; j < m; j++)
for(int k = 0; k < (1 << m); k++)
if(k & 1 << j)
sum[k] = (sum[k] + sum[k ^ (1 << j)]) % mod;//求k的子集的和