mutiset 哈希 思维
D. GCD of an Array
https://codeforces.com/problemset/problem/1493/D
题意:给定一个序列,有q次询问,每次将其中一个数×上ki,问每次操作后全体gcd为多少。
n,q<=2e5.
题解:这题思路很一眼,无非是维护每一个数的素因数以及其指数,存每个数中素因数出现的次数,每次操作判断改变后的素因数集合中最小元素。问题主要是实现,具体而言我们可以用map[i]维护每一个数的素因数出现次数,用mutiset[p]维护素数p在各个a[i]中出现的次数,若没出现则不放入,每次求最小元素即可,单次询问复杂度为log*log。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int const maxn = 2e5 + 5, max_val = 2e5 + 5;
ll mod = 1e9 + 7, ans = 1;
int nxt[max_val], n;
multiset <int> cnt[max_val];
map <int, int> cnt_divisor[maxn];
void add(int i, int x) {
while (x != 1) {
int div = nxt[x], add = 0;
while (nxt[x] == div) add++, x = x / nxt[x];
int lst = cnt_divisor[i][div];
cnt_divisor[i][div] += add;
int lst_min = 0;
if ((int)cnt[div].size() == n) {
lst_min = (*cnt[div].begin());
}
if (lst != 0) {
cnt[div].erase(cnt[div].find(lst));
}
cnt[div].insert(lst + add);
if ((int)cnt[div].size() == n) {
for (int j = lst_min + 1; j <= (*cnt[div].begin()); ++j) {
ans = ans * (ll)div % mod;
}
}
}
}
main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
int q, l, x;
cin >> n >> q;
for (int i = 2; i < maxn; ++i) {
if (nxt[i] == 0) {
nxt[i] = i;
if (i > 10000) continue;
for (int j = i * i; j < maxn; j += i) {
if (nxt[j] == 0) nxt[j] = i;
}
}
}
for (int i = 1; i <= n; ++i) {
cin >> x;
add(i, x);
}
for (int i = 1; i <= q; ++i) {
cin >> l >> x;
add(l, x);
cout << ans << '\n';
}
return 0;
}
B. BinCoin 哈希
https://codeforces.com/problemset/problem/1773/B
题意:给定n个点,组成一棵二叉树,给出k个排列,每个表示对这个二叉树的一种遍历,具体而言,从根节点开始,若该点无子节点,则记录后传给父节点,否则随机选择一个儿子下发,再次传上时记录。还原该树。
n<=1000,50<=k<=100
题解:这题的性质也比较好观察,即对一颗子树,其根节点左右两侧元素集合总是那两个集合,那么我们可以每次找到根后拆分问题求解,但直接做复杂度为nnnk,问题在于对集合的处理。我们对点做哈希,hash[i]=2^i%mod.再对于每个排列计算前缀和,这样我们的询问不需要nk,只需要k即可,最后总复杂度为nn*k.
E. Node Pairs
https://codeforces.com/problemset/problem/1763/E
题意:我们称有序对为u!=v&u->v可达但v->u不可达。我们定义p可达图为图中有p对点两两可互相到达,则现在给你一个数k,问需要构建一张图k可达,至少需要多少个点,同时,再满足最少点数情况下,最多有多少有序对。
k<=2e5
题解:观察发现,组成可达对的点是又一组组完全图组成的,换言之,我们即为求将k分解为若干个n(n-1)/2形式的数之和,问最小点数。我们可以dp求解,dp(i)表示k=i时,表示k所需的最小点数,f(i)=min(f(i),f(i-a(j))+j),a(j)表示为j个点形成的可达对数量,由于 a[j]=j(j-1)/2,故复杂度为n*sqrt(n).
而对于如何最大化有序对,即可转化为现有w个点,每个点有一个权值,u->v使得答案增加二者之积,且图为有向无环图。我们可以贪心地得到答案,我们每次取集合中最大的数,将其与所有剩下的数连边,这样结果必定最大且不存在环,证明很显然,留给读者。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10,mod=1e9+7;
int a[N],b[N],f[N],pre[N],s[N];
signed main(){
int n;cin>>n;
for(int i=2;i<=10000;i++){
a[i]=(i-1)*i/2;
}
for(int i=1;i<=n;i++) f[i]=1e18;
for(int i=1;i<=n;i++){
for(int j=2;;j++){
if(a[j]>i) break;
if(f[i]>f[i-a[j]]+j) pre[i]=j;
f[i]=min(f[i],f[i-a[j]]+j);
}
}
int x=n,t=0;
while(x){
int w=pre[x];
b[++t]=w;
x-=a[w];
}
cout<<f[n]<<" ";
sort(b+1,b+t+1);
for(int i=1;i<=t;i++){
s[i]=s[i-1]+b[i];
}
int ans=0;
for(int i=t;i>=1;i--){
ans+=b[i]*(s[i-1]);
}
cout<<ans;
}
E. A-Z Graph
https://codeforces.com/contest/1494/problem/E
题意:给定一个包含 n 个顶点的有向图。每个有向边(或弧)都标有一个字符。初始时,图为空。
你需要处理 m 个查询。每个查询有三种类型之一:1,u,v🗡添加一条标为c的边,2,u,v间的边删除,3,问是否存在长度为k的路径使得v1,v2,,,vk组成的字符串与vk,,,v1的字符串一致,点可以被重复遍历,保证没有重边。
n,m<=2e5,k<=1e5.
题解:但k为奇时,v1,v2,v1,v2,v1形如这样的即可满足题意,若无两点可互相到达,则无解。
若k为偶数,则有奇数条边,我们观察后可发现,若有这种路径,则中间必有两点u,v满足(u,v)=(v,u)=c,即同颜色回路,此时我们只需按这个回路转圈即可,故等价于存在同色回路。
由于每条边 (u,v) 最多属于一个 {u,v} 对,我们只需维护两个集合,一个用于具有不同标记的对,另一个用于具有相同标记的对。如果 k 是奇数,我们检查至少一个集合不为空。如果 k 是偶数,我们检查第二个集合不为空。
#include<bits/stdc++.h>
using namespace std;
#define fore(i, l, r) for(int i = int(l); i < int(r); i++)
#define sz(a) int((a).size())
#define x first
#define y second
typedef pair<int, int> pt;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, m;
cin >> n >> m;
map<pt, int> allEdges;
set<pt> difPairs, eqPairs;
fore (q, 0, m) {
char tp; cin >> tp;
if (tp == '+') {
int u, v;
char c;
cin >> u >> v >> c;
if (allEdges.count({v, u})) {
if (allEdges[{v, u}] == c)
eqPairs.emplace(min(u, v), max(u, v));
else
difPairs.emplace(min(u, v), max(u, v));
}
allEdges[{u, v}] = c;
} else if (tp == '-') {
int u, v;
cin >> u >> v;
if (eqPairs.count({min(u, v), max(u, v)}))
eqPairs.erase({min(u, v), max(u, v)});
if (difPairs.count({min(u, v), max(u, v)}))
difPairs.erase({min(u, v), max(u, v)});
allEdges.erase({u, v});
} else {
int k;
cin >> k;
bool hasAns = !eqPairs.empty();
if (k & 1)
hasAns |= !difPairs.empty();
cout << (hasAns ? "YES" : "NO") << '\n';
}
}
return 0;
}