绵阳东辰国际test201910.22am

分析:

陷入Tarjan太深,浪费了很多无用的时间

很明显如果是一棵树的话,直接都染黑了

然后就想到要将每一个连通块都变为一棵树

设连通块总数为C,总共有m条边,有用的就只有n-C条

所以要染色的就有m-n+C直接并查集维护

总结:当时真的是脑残,去找环了

分析:

首先答案是很多个数中找一个二分一个答案M

问题转化为求 [1,M] 内有多少个数字在至少一个S(ni) 里面。

如果是可重复的话直接计算有多少个数在S里面就好

是不可重复的当然就要考虑容斥

补充:容斥原理

我们考虑先计算出有多少个 [1,M] 中的数在 S(n1),S(n2),··· ,S(nk) 中,

然后把它们都加起来。 显然这样算多了:

如果某个数字同时在多个 S(ni) 里面,那么他就重复了。

于是 我们考虑再去掉在至少两个 S(ni) 中的,

然后加上至少三个 S(ni) 中的,

去掉至少四 个 S(ni) 中的……

再考虑一个数如果既是2的次幂,又是3的次幂,那么它也一定是lcm(2,3)=6的次幂

code by chitongzi:

#include <bits/stdc++.h>
#define xx first
#define yy second
#define ll long long
#define mp make_pair
#define pb push_back
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define int long long

using namespace std;

inline long long fpow (long long base, long long v)
{
	long long tot = 1;
	while (v)
	{
		if (v & 1) {
			if (1000000000000000000LL / base < tot) return 1LL << 60;
			tot *= base;
		}
		base *= base;
		v >>= 1;
		if (!base) return v ? 1LL << 60 : tot;
		if (v > 1 && tot > 1000000000LL) return (1LL << 60);
	}
	return tot;
}

inline int gcd (int u, int v)
{
	return !v ? u : gcd (v, u % v);
}

const int N = 65;
int g[N][N], n, m, a[N];
long long dp[N][N];

inline long long check (long long k)
{
	long long res = 0;
	for (int i = 1; i <= 60; ++i)
	{
		if (!dp[n][i]) continue;
		long long p = pow (k, 1.0 / i);
		while (fpow (p, i) > k) p--;
		while (fpow (p + 1, i) <= k) p++;
		p--;//here
		res += dp[n][i] * p;
	}
	return res + 1;
}

signed main ()
{
	for (int i = 1; i <= 60; ++i)
		for (int j = 1; j <= 60; ++j)
			g[i][j] = i * j / gcd (i, j);
	int q;
	scanf ("%lld", &q);
	while (q--)
	{
		scanf ("%lld%lld", &m, &n);
		for (int i = 1; i <= n; ++i)
			scanf ("%lld", &a[i]);
		memset (dp, 0, sizeof dp);
		dp[0][0] = -1;
		for (int i = 0; i < n; ++i)
		{
			for (int j = 0; j <= 60; ++j)
			{
				if (!dp[i][j]) continue;
				dp[i + 1][j] += dp[i][j];
				int lcm = j ? j / gcd (j, a[i + 1]) * a[i + 1] : a[i + 1];
				if (lcm > 60) continue;
				dp[i + 1][lcm] -= dp[i][j];
			}
		}
//		for (int i = 0; i <= n; ++i, puts (""))
//			for (int j = 0; j <= 20; ++j)
//				printf ("%lld ", dp[i][j]);
		long long l = 1, r = 100000000000000000LL, mid;
		while (l < r)
		{
			mid = (l + r) / 2;
			if (check (mid) < m) l = mid + 1;
			else r = mid;
		}
		printf ("%lld\n", l);
	}
	return 0;
}

按位与

我们按照从高位到低位的顺序进行贪心

如果当前位有至少两个数字是 1,那么 我们可以让这一位是 1

因此最终答案的这一位一定是 1。

所 以我们可以去掉所有这一位为 0 的数字,然后继续向下贪心即可。

最后的方案数就是 剩余的数字中选出两个的方案数。

按位异或

这样就是01trie树的经典例题,但貌似我打卦了

按位或

同样地在确定了一个数之后从高到低贪心

但是这里又多了一个问题:如果这个 数字的当前位是 1,那么我们仍然没法减少候选范围

然后就是什么高维前缀和乱搞

高维前缀和题外话:

子集和
for(int j=0;j<n;j++)

for(int i=0;i<1<<n;i++)

if(i&(1<<j)) dp[i]+=dp[i^(1<<j)];

超集和

posted @ 2019-10-23 16:38  wzx_believer  阅读(141)  评论(0编辑  收藏  举报