主席遗留下来的遗产~
(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
", 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
", 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;
}