2019正睿csp-s赛前冲刺

主席遗留下来的遗产~

\(Day5\)

杜爷删代码的时候找到了一个去年他不会的题,所以拿来看看觉得还挺有意思,就是\(C\)

然后切完\(C\)就顺便把\(A\)\(B\)也补了。

吐槽下奇怪的难度顺序:\(B > A > C\)

\(A\)

如题每个连通子图最后只会剩下\(n - 1\)条边,所以答案就随便算了。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int N = 100000;
const int M = 200000; 

int n, m, head[N + 50], num, vis[N + 50], ans, cnt;

struct Node
{
	int next, to;
} edge[M * 2 + 50];

void Addedge(int u, int v)
{
	edge[++num] = (Node){head[u], v};
	head[u] = num;
	return;
} 

void Read(int &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
	x = p ? -x : x;
	return;
}

void Dfs(int x, int fa)
{
	vis[x] = 1; cnt++;
	for (int i = head[x]; i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (v == fa) continue;
		if (!vis[v]) Dfs(v, x);
	}
	return;
}

int main()
{
	Read(n); Read(m);
	for (int i = 1, u, v; i <= m; i++) 
	{
		Read(u), Read(v);
		if (u == v) continue; 
		Addedge(u, v), Addedge(v, u);
	}
	for (int i = 1; i <= n; i++) 
		if (!vis[i])
		{
			cnt = 0;
			Dfs(i, 0);
			ans += cnt - 1;
		}
	printf("%d", m - ans);
	return 0;
}

\(B\)

大概是最难的一题。

直接算显然非常困难,看到\(k\)很小于是考虑二分判断答案。

那么就是求出\(<= n\)的集合中有多少个数。

发现每个单独算加起来是不行的,有很多数是重合的,于是容斥。

但是\(2^50\)显然不行,注意到值域也很小,然后计算有多少个数的时候实际上要对\(n\)开根,这样容斥集合里的数的\(lcm\)如果\(> 60\)就没用了。

所以只需要关心\(lcm <= 60\)的容斥系数,这样直接背包转移。

但是\(1\)要单独拿出来算,因为如果\(lcm > 60\)\(1\)还是会存在,这样容斥的时候就会漏不少东西。

#include <iostream> 
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;

#define ll long long
#define int long long

int n, k, a[70], dp[70][70], m, lcm[70][70];

void Read(int &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
	x = p ? -x : x;
	return; 
}

int Gcd(int a, int b)
{
	return a % b == 0 ? b : Gcd(b, a % b);
}

int Lcm(int a, int b)
{
	return a * b / Gcd(a, b);
}

void Prework()
{
	for (int i = 1; i < k; i++)
		for (int j = 60; j >= 1; j--)
		{
			if (lcm[a[i + 1]][j] <= 60) dp[i + 1][lcm[a[i + 1]][j]] -= dp[i][j];
			dp[i + 1][j] += dp[i][j];
		} 
	return;
}

ll Ksm(ll a, int b)
{
	ll tmp = 1;
	while (b)
	{
		if (b & 1) tmp = tmp * a;
		a = a * a;
		b >>= 1;
	}
	return tmp;
}

ll Div(ll x,int y)
{
	int k = pow(x, 1.0 / (double)y), k1 = k - 1, k2 = k + 1;
	ll p = Ksm(k, y), p1 = Ksm(k1, y), p2 = Ksm(k2, y);
	if(p2 > p && p2 <= x)return k2;
	if(p <= x) return k;
	return k1;
}

int Check(ll pd)
{
	ll tmp = 0;
	for (int j = 1; j <= 60; j++)
		if (dp[k][j]) tmp = tmp + (ll)(Div(pd, j) - 1LL) * dp[k][j];
	return tmp + 1 >= (ll)m;
}

signed main()
{
	int t, flag1;
	Read(t);
	for (int i = 1; i <= 60; i++)
		for (int j = 1; j <= 60; j++) 
			lcm[i][j] = Lcm(i, j); 
	while (t--)
	{
		flag1 = 0;
		Read(m); Read(k);	
		memset(dp, 0, sizeof(dp));	
		for (int i = 1; i <= k; i++) 
		{	
			Read(a[i]), dp[i][a[i]] = 1;
			if (a[i] == 1) flag1 = 1;
		}
		if (flag1 || m == 1) { printf("%lld\n", m); continue; }
 		ll l = 1, r = 1e17;
		Prework();
		while (l < r)
		{
			ll mid = (l + r) >> 1;
			if (Check(mid)) r = mid;
			else l = mid + 1;
		}
		printf("%lld\n", l);
	}
	return 0;
}

\(C\)

杜爷说完之后秒了异或和按位与的情况,按位或的情况大概也想出来了,但是没系统学过\(SOS\ dp\)所以没法实现。

学完\(SOS\ dp\)就直接从高位往地位贪心,随便算算贡献就好了。

反正是板子题。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int N = 100000;
const int M = 8388608; 

int n, q;

void Read(int &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
	x = p ? -x : x;
	return;
}

namespace Sub1
{
	int a[N + 50], p[N + 50];
	void Solve()
	{
		int flag;
		for (int i = 1; i <= n; i++) Read(a[i]);
		int ans = 0, num = n;
		for (int i = 22; i >= 0; i--)
		{
			flag = 0;
			for (int j = 1; j <= n; j++)
				if (!p[j] && ((a[j] >> i) & 1)) flag++;
			if (flag >= 2) 
			{
				ans |= (1 << i);
				for (int j = 1; j <= n; j++)
					if (!((a[j] >> i) & 1) && !p[j])
						p[j] = 1, num--;					
			}
		}
		printf("%d %lld", ans, 1LL * num * (num - 1) / 2);
		return;
	}
}

namespace Sub2
{
	long long ans = 0;
	int trie[30000050][2], cnt[30000050], tot = 0, max = 0;
	void Insert(int x)
	{
		int now = 0;
		for (int i = 22; i >= 0; i--)
		{
			int c = (x >> i) & 1;
			if (!trie[now][c]) trie[now][c] = ++tot;
			now = trie[now][c];
		}
		cnt[now]++;
		return;
	}
	void Qmax(int x)
	{
		int tmp = 0, now = 0;
		for (int i = 22; i >= 0; i--)
		{
			int c = (x >> i) & 1;
			if (trie[now][!c]) tmp |= (1 << i), now = trie[now][!c];
			else now = trie[now][c];
 		}
 		if (max == tmp) ans += cnt[now];
 		else if (max < tmp) max = tmp, ans = cnt[now];
 		return;
	}
	void Solve()
	{
		for (int i = 1, x; i <= n; i++) Read(x), Insert(x), Qmax(x);
		printf("%d %lld", max, ans);
		return;
	}
}

namespace Sub3
{
	int dp[M + 50], max = 0, a[N + 50];
	long long ans = 0;
	void Solve()
	{
		for (int i = 1; i <= n; i++) Read(a[i]), dp[a[i]]++;
		for (int i = 0; i < 23; i++)
			for (int j = 0; j < M; j++)
				if (!((j >> i) & 1)) dp[j] += dp[j | (1 << i)];
		for (int i = 1; i <= n; i++)
		{
			int t = 0;
			for (int j = 22; j >= 0; j--)
			{
				if ((a[i] >> j) & 1) continue;
				if (dp[t | (1 << j)]) t |= (1 << j);		
			}
			if (max < (a[i] | t)) max = (a[i] | t), ans = dp[t] - (!t);
			else if (max == (a[i] | t)) ans += dp[t] - (!t);
		}
		printf("%d %lld", max, ans / 2);
		return;
	}
}

int main()
{
	Read(n); Read(q);
	if (q == 1) Sub1::Solve();
	else if (q == 2) Sub2::Solve();
	else if (q == 3) Sub3::Solve();
	return 0;
}
posted @ 2020-10-12 16:10  Tian-Xing  阅读(62)  评论(0编辑  收藏  举报