C. Nikita and LCM
题目:
思路:
非常好的思维题,顺便复习了一下快速枚举因数和lcm的性质
我们先来看答案的上界,即全选,此时说明 lcm(a1,a2,a3,...) > a_max 其中 a_max 为 a 中最大的数,那么如果答案不是 n 呢?
既然不是 n,也就是说如果我们全选的话这个数会出现在 a 中,而如果要出现在 a 中,显然这个数一定是 a_max
那么我们可以假设最后不在 a 中的 lcm 为 m,而根据 lcm 性质我们可以知道 子序列的lcm 肯定是整个序列lcm 的一个 因数 x,所以我们可以枚举 a_max 的因数,如果对于 a[i] 有 x % a[i] == 0,说明我们可以选 a[i], 然后快速枚举以及遍历一遍累加答案,最后取最大值即可,由于 n 是 2000,所以时间复杂度是可以接受的
特别注意,由于因子可能存在原数组,所以我们还要用 map 存储一下出现过的数字
代码:
#include <iostream>
#include <algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <memory>
using namespace std;
#define int long long
#define yes cout << "Yes\n"
#define no cout << "No\n"
int gcd(int a,int b)
{
return !b ? a : gcd(b, a % b);
}
int lcm(int a,int b)
{
return a * b / gcd(a,b);
}
void solve()
{
int n;
cin >> n;
vector<int> a(n);
map<int, int> mp;
int LCM = 1,mx=0;
for (int i = 0; i < n; i++)
{
cin >> a[i];
mp[a[i]]++;
mx = max(mx, a[i]);
}
for (int i = 0; i < n; i++)
{
LCM = lcm(LCM, a[i]);
if (LCM > mx)
{
cout << n << endl;
return;
}
}
vector<pair<int, int>> b;
for (auto& x : mp)
b.push_back(x);
auto fuc = [&](int x) ->int{
int Lcm = 0, cnt = 0;
for (auto& son : b)
{
if (x % son.first == 0)
{
cnt += son.second;
if (Lcm == 0)
Lcm = son.first;
else
Lcm = lcm(Lcm, son.first);
}
}
if (Lcm != x)cnt = 0;
return cnt;
};
int res = 0;
for (int i = 1; i*i <= mx; i++)
{
if (mx % i == 0)
{
if (!mp[i])
res = max(res, fuc(i));
if (!mp[mx/i])
res = max(res, fuc(mx/i));
}
}
cout << res << endl;
}
signed main()
{
cin.tie(0)->sync_with_stdio(false);
int t = 1;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
D. XORificator
题目:
思路:
无敌哈希运用,学到了新的想法,值得反复观看
题目告诉我们可以使用操作使得每一行的 0/1 翻转,然后问我们在所有的操作可能中最后最多有多少个特殊列,其中特殊列为该列中只有一个 1
我们首先发现,好像除了暴力枚举没啥法子能做了?事实上确实是这样,所以我们得想想怎么优化比较好
我们发现,如果我们钦定某个 (i,j) 为 1,那么全局情况就唯一了,那我们可以枚举每一个点,如果要让他变成这一列唯一的 1 我们要怎么操作,然后把操作存储下来,而这个操作每一次只有变换或者不变换,即可以看作一个 01 串,那我们就可以使用字符串哈希的类似方法来处理
具体的,我们先获取如果要让这一列全变成 0 要执行什么操作,然后枚举每一位让他取反,这样我们就知道如果要只让他为 1 的操作是什么呢,那么如何储存哈希值呢?我们为了防止哈希冲突,我们可以使用双哈希,我们定义执行操作为取这一点的哈希,这样我们就能获得对应的两个哈希了,然后我们用一个 map 存储对应操作的答案,这样后续遇到相同的哈希值(即相同的操作时)我们就知道谁的答案最大了,同时我们还要储存此时是让哪个 (i,j) 为 1,这样便于我们输出答案
具体实现看代码,还是有点细节的,注意哈希值这里是随机数,这样不容易被数据仙人卡
代码:
#include <iostream>
#include <algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <memory>
#include <random>
#include <ctime>
using namespace std;
#define int long long
#define yes cout << "Yes\n"
#define no cout << "No\n"
mt19937_64 rnd(time(0));
void solve()
{
int n, m;
cin >> n >> m;
vector<string> ma(n);
for (int i = 0; i < n; i++)
{
cin >> ma[i];
}
vector<int> hash1(n), hash2(n);
for (int i = 0; i < n; i++)
{
hash1[i] = rnd(), hash2[i] = rnd();
}
map<pair<int, int>, int> hash;
int res = 0;
pair<int, int> point = { 0,0 };
for (int j = 0; j < m; j++)
{
int h1 = 0, h2 = 0;
//计算将这一列全变成 0 的操作的哈希值
for (int i = 0; i < n; i++)
{
if (ma[i][j] == '1')
h1 ^= hash1[i], h2 ^= hash2[i];
}
for (int i = 0; i < n; i++)
{
//如果第 i 行的为 1 的操作的哈希值
h1 ^= hash1[i], h2 ^= hash2[i];
hash[{h1, h2}]++;
if (res < hash[{h1, h2}])
{
res = hash[{h1, h2}];
point = { i,j };
}
//变回全是 0 的哈希值
h1 ^= hash1[i], h2 ^= hash2[i];
}
}
cout << res << endl;
for (int i = 0; i < n; i++)
{
if (ma[i][point.second] == '1')
cout << (i != point.first);
else
cout << (i == point.first);
}
cout << endl;
}
signed main()
{
cin.tie(0)->sync_with_stdio(false);
int t = 1;
cin >> t;
while (t--)
{
solve();
}
return 0;
}