E. Delete a Segment
题意:
给你n个线段进行区间覆盖,删除线段\(seg_i\)后,其余线段覆盖区间有还有\(cnt_i\)段连续的,求\(cnt_1-cnt_n\)中的最大值。
解法:
整数点线段覆盖区间,根据套路,将端点乘上2,乘上2加一,乘上2减一全部推进离散化。
然后对每条线段覆盖区域进行差分。此时,若不删去线段,要求有多少段连续覆盖,就是求有多少段连续区域不为0.
考虑删去一条线段,这条线段有多少连续的值为1的段,删去这条线段后就会多出几个区间。再考虑线段两端本来就是断着的减去即可。
#include <bits/stdc++.h>
#define all(x) (x).begin(),(x).end()
using namespace std;
const int maxn = 1e6;
int has[2 * maxn + 11],sum[2 * maxn + 11],l[2 * maxn + 11],r[2 * maxn + 11];
vector <int> point;
void push(int x) {
x *= 2;
point.emplace_back(x - 1);
point.emplace_back(x + 1);
point.emplace_back(x);
}
int main(){
int t;
scanf("%d" , &t);
while (t--) {
int n;
scanf("%d" , &n);
point.clear();
for (int i = 1; i <= n; i++) {
scanf("%d %d",&l[i],&r[i]);
push(l[i]);
push(r[i]);
}
sort(all(point));
point.erase(unique(all(point)) , point.end());
int m = point.size();
for (int i = 1; i <= n; i++) {
int L = lower_bound(all(point) , l[i] * 2) - point.begin() + 1;
int R = lower_bound(all(point) , r[i] * 2) - point.begin() + 1;
sum[R + 1]--; sum[L]++;
}
for (int i = 1; i <= m; i++) sum[i] += sum[i - 1];
for (int i = 1; i <= m; i++)
if (sum[i - 1] != 1 && sum[i] == 1) has[i] = has[i - 1] + 1; else has[i] = has[i - 1];
int ans = 0;
bool flag = false;
for (int i = 1; i <= m; i++)
if (sum[i] == 0) { if (flag) ans++; flag = false; } else flag = true;
int mx = 0;
for (int i = 1; i <= n; i++) {
int L = lower_bound(all(point) , l[i] * 2) - point.begin() + 1;
int R = lower_bound(all(point) , r[i] * 2) - point.begin() + 1;
int cnt = has[R] - has[L - 1];
if (sum[L] == 1 && sum[L - 1] == 1) cnt++;
if (sum[R] == 1 && sum[R + 1] == 0) cnt--;
if (sum[L] == 1 && sum[L - 1] == 0) cnt--;
mx = max(mx , ans + cnt);
}
printf("%d\n" , mx);
for (int i = 1; i <= m; i++) sum[i] = 0,has[i] = 0;
}
}
F. Classical?
题意:
解法:
\(lcm(i,j)=\frac {i*j}{gcd(i,j)}=\frac {i}{gcd(i,j)}*\frac{j}{gcd(i,j)}*gcd(i,j)\)。所以我们考虑枚举gcd,假设为g。对于每个g,要使lcm最大,就是要使剩下来的互质的两个数最大。对于每个g,我们考虑从大到小枚举倍数,并且用一个栈来模拟这个过程,栈里始终只保存还可能出现更优解的倍数。如果已经枚举过的某个倍数j与现在枚举到的倍数i互质的话,一个有效答案gij就出现了,此时,i后面的数不管是否与i,j互质,与i或j产生的答案都不会更大了,所以i,j在之后枚举中就不用考虑了,并且所有小于j的数都不会产生更优的解,我们也可以将他们弹出栈。现在我们要考虑的就是找到什么时候为止了。我们只需要知道栈中有多少个与当前倍数i互质的数就行了。对于栈内的数动态维护,利用莫比乌斯函数容斥一下就可以得到了。
比如说,我们考虑与30互质的数的个数,我们减去栈中2的倍数,减去栈中3的倍数,减去栈中5的倍数,在加上6的倍数,10的倍数,15的倍数,减去30的倍数。加或者减其实就是莫比乌斯函数,所以我们维护倍数,莫比乌斯函数作为加减号的确定依据,容斥得到答案就可以了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5;
int cnt = 0;
int prime[maxn + 11];
bool vis[maxn + 11] = {false};
int res[maxn + 11],mu[maxn + 11];
vector <int> d[maxn + 11];
void pre() {
for (int i = 1; i <= maxn; i++)
for (int j = i; j <= maxn; j += i)
d[j].emplace_back(i);
mu[1] = 1;
for (int i = 2; i <= maxn; i++) {
if (!vis[i]) { prime[++cnt] = i; mu[i] = -1; }
for (int j = 1; j <= cnt; j++) {
if (i * prime[j] > maxn) break;
vis[i * prime[j]] = true;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for (int i = 1; i <= maxn; i++) vis[i] = false;
}
int gcd(int a,int b) { return b == 0 ? a : gcd(b , a % b); }
void upd(int x,int val) { for (auto i : d[x]) res[i] += val; }
int calc_prime(int x) {
int ans = 0;
for (int i : d[x]) ans += res[i] * mu[i];
return ans;
}
int main(){
pre();
int n;
scanf("%d" , &n);
long long ans = 0;
for (int i = 1; i <= n; i++) {
int x;
scanf("%d" , &x);
ans = max(ans , 1ll * x);
vis[x] = true;
}
for (int g = 1; g <= maxn; g++) {
stack <int> s;
for (int i = maxn / g; i >= 1; i--) {
if (!vis[i * g]) continue;
int num = calc_prime(i);
bool in = num == 0 ? true : false;
while (num) {
if (gcd(s.top() , i) == 1) {
ans = max(ans , 1ll * i * s.top() * g);
num--;
}
upd(s.top() , -1);
s.pop();
}
if (in) { upd(i , 1); s.push(i); }
}
while (!s.empty()) {
upd(s.top() , -1);
s.pop();
}
}
printf("%lld\n" , ans);
}