CF1834 题解

发布时间 2023-06-27 22:13:26作者: The_Last_Candy

CF1834 题解

A

考虑答案与元素位置无关,只与\(1\)\(-1\)的个数有关。要求\(1\)必须多于或等于\(-1\),并且\(-1\)个数为偶数。分讨:

序列中\(num(1) \geq num(-1)\),只需要看\(num(-1)\)正负性,奇数1步,偶数0步

序列中\(num(1) < num(-1)\),先通过\(-1\)\(1\)将数目补到第一种情况,再做第一种情况即可。

时间复杂度\(O(n)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 105;
int a[N],n,s[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n;
		memset(a,0,sizeof(a));
		memset(s,0,sizeof(s));
		for(int i = 1;i <= n;i++) cin>>a[i],s[i] = s[i - 1] + a[i];
		if(s[n] >= 0) cout<<((n - s[n]) % 4 == 0 ? 0 : 1)<<endl;
		else cout<<((n / 2) % 2 == 0 ? (-s[n] + 1) / 2 : (-s[n] + 1) / 2 + 1)<<endl;
	}
	return 0;
}

B

考虑\(9\)\(0\)对应一定是最大的,我们尽量凑成\(9\)\(0\)配对的情况。将\(L\)\(R\)用前导0补齐,我们不难发现在前面若干个相同的数位是无法产生贡献的,直到第一个不同的数位时,将\(L\)往上取,这一位后面全部赋为9,\(R\)往下取,这一位后面全部赋为0,这样就保证这一位的贡献没有下降,后面的贡献都取到了最大值,从而是最优解。

时间复杂度\(O(n)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 105;
int n,m;
char a[N],b[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		scanf("%s",a + 1);
		scanf("%s",b + 1);
		n = strlen(a + 1);
		m = strlen(b + 1);
		if(n < m) 
		{
			for(int i = n;i >= 1;i--) a[i + m - n] = a[i];	
			for(int i = 1;i <= m - n;i++) a[i] = '0';
		}
		int ans = 0;
		for(int i = 1;i <= m;i++)
			if(a[i] != b[i])
			{
				ans += (m - i) * 9 + abs(b[i] - a[i]);
				break;
			}
		cout<<ans<<endl;
	}
	return 0;
}

C

既然Alice想要将步数最小化,那么她就要尽量将两个字符串之间的差异消掉。我们发现\(S,T\)的对应方式只有两种\((s_1 \to t_1,s_n\to t_n \ 和\ s_1\to t_n,s_n\to t_1)\)。所以Bob翻转哪个串都只是改变了一次对应方式,如果Alice一开始就按照一种对应方式消除差异的话,Bob完全干扰不了Alice,所以得出结论:Bob是小丑,最多只能将步数拖1步,选择权在于Alice。

宁愿当小丑也在所不惜,全力以赴地进行着必输的游戏......仅仅是他深爱着她。

上文说了,只有两种对应方式,所以Alice自然选的就是步数小的那个。我们发现由于Alice改变一次,Bob就要翻转一次,所以步数等于对应方式的差异数乘2,根据差异数的奇偶性和对应顺序决定是否需要-1(Bob最后一次翻之前就对应上了)

时间复杂度\(O(n)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
char s[N],t[N];
int n;
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n;
		scanf("%s",s + 1);
		scanf("%s",t + 1);
		int dif = 0,rdif = 0,step = 0,rstep = 0;
		for(int i = 1;i <= n;i++) if(s[i] != t[i]) ++dif;
		for(int i = 1;i <= n;i++) if(s[i] != t[n + 1 - i]) ++rdif;
		step = dif << 1;
		if(dif & 1) step--;
		if(dif == 0) step = 0;
		rstep = rdif << 1;
		if(!(rdif & 1)) rstep--;
		if(rdif == 0) rstep = 2;
		cout<<min(step,rstep)<<endl;
	}
	return 0;
}

D

考虑到对于一个人,如果他是极差中的最大值,那么所选的点就是他学过的所有点(对于学过的,多选一个他的高度就+1,最小值的高度可能+1,一定不劣;对于他没学过的,选了以后他的高度不变,最小值的高度可能+1,一定不优)。

所以我们可以枚举这个最大值是谁,可能的最小值与这个人可能有3种关系:不交,相交,最小值包含于这个人。先不考虑包含,这一部分很简单,我们尽量选一个人让他们不交,所以记录全场最大右端点和最小左端点即可,因为取这两个端点对应的线段一定是与中间的最大值交集最小的,与两边的交集大小取\(min\)就是交集大小了。

对于包含关系,我们发现与最大值交集最小的,一定是包含于他的人中学的最少的人,考虑二维关系不好维护,就将人按照右端点排序,从小到大遍历右端点,在以左端点为下标的树状数组中找比当前左端点大的当中学的最少的人,就是这个最大值对应的答案。

时间复杂度\(O(n\log n)\)

解释:代码中对于“比当前左端点大的”这个后缀操作,在离散化左端点时将其反序存储,转化为树状数组的前缀问题。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5,inf = 0x3f3f3f3f;
struct Seg{
	int l,r;
}x[N];
int n,maxl,minr,m,xl[N],cnt = 0,inc[N];
map<int,int> rev;
struct BIT{
	int a[N];
	inline void init()
	{
		for(int i = 1;i <= n;i++) a[i] = inf;
	}
	inline int lowbit(int x)
	{
		return x & (-x);
	}
	inline int query(int x)
	{
		if(!x) return inf;
		return min(query(x - lowbit(x)),a[x]);
	}
	inline void modify(int x,Seg k)
	{
		if(x > cnt) return;
		a[x] = min(a[x],k.r - k.l + 1);
		modify(x + lowbit(x),k);
	}
}p;
inline bool cmp(Seg a,Seg b)
{
	if(a.r ^ b.r) return a.r < b.r;
	else return a.l > b.l;
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		maxl = 0,minr = 2e9;cnt = 0;
		rev.clear();
		p.init();
		for(int i = 1;i <= n;i++)
		{
			cin>>x[i].l>>x[i].r;
			xl[i] = x[i].l;
			maxl = max(maxl,x[i].l);
			minr = min(minr,x[i].r);
		}
		sort(xl + 1,xl + n + 1);
		cnt = unique(xl + 1,xl + n + 1) - (xl + 1);
		for(int i = 1;i <= cnt;i++) rev[xl[i]] = cnt + 1 - i;
		sort(x + 1,x + n + 1,cmp);
		for(int i = 1;i <= n;i++)
		{
			inc[i] = p.query(rev[x[i].l]);
			p.modify(rev[x[i].l],x[i]);
		}
		int ans = 0;
		for(int i = 1;i <= n;i++)
		{
			int inter = min(min(max(x[i].r - maxl + 1,0),max(minr - x[i].l + 1,0)),inc[i]);
			ans = max(ans,(x[i].r - x[i].l + 1 - inter) << 1);
		}
		cout<<ans<<endl;
	}
	return 0;
}

E

我们发现如果一个素数是一些数的\(lcm\),当且仅当这个素数单独作为一个元素出现了,所以答案一定不超过第\(n + 1\)个素数的大小,我们发现第\(3e5\)个素数大致在\(6e6\)左右。

考虑到一个暴力,对于每个点\(i\)开始,暴力向右扫描\(j\),求出\([i,j]\)中元素的\(lcm\)并加进桶里,如果\(lcm\)大于了第\(n + 1\)个素数的大小,就停止对\(j\)的扫描(因为数更多\(lcm\)一定不减)。

这样暴力复杂度是\(O(n^2\log n)\)的,无法通过,我们发现我们只需要检查一个\(lcm\)是否出现过,想办法在向右扫的时候跳过\(lcm\)相同的一段,这个可以预处理一个\(lcm\)\(st\)表,然后每次倍增来跳过,理论上比原来还多一个\(\log\)

按这个写一发,你就会发现你AC了。

因为这样优化后,时间复杂度就有了保证,考虑向右扫时每次\(lcm\)的变化都会让\(lcm\)的值起码乘上2,所以最多只会变化\(O(\logV)\)次(\(V\)是值域)。单次扫描复杂度就降为了\(O(\log n \log V)\),总复杂度\(O(n\log n\log V)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5,M = 6e6 + 5;
typedef long long ll;
ll a[N],st[20][N],n;
int prime[N * 5],tot = 0,lim;
bool vis[M];
inline void init()
{
	for(int i = 2;i <= M - 1;i++)
	{
		if(!vis[i])
			prime[++tot] = i;
		for(int j = 1;j <= tot && 1ll * i * prime[j] <= M - 1;j++)
		{
			vis[i * prime[j]] = 1;
			if(!(i % prime[j])) break;
		}
	}
	memset(vis,0,sizeof(vis));
}
inline int lcm(int x,int y)
{
	if(x == -1 || y == -1) return -1;
	ll p = 1ll * x / __gcd(x,y) * y;
	if(p > lim) return -1;
	return p;
}
inline int fd(int x,int &val)
{
	for(int i = 19;i >= 1;i--)
		if(lcm(val,st[i][x]) == val) x += (1 << i) - 1;
	val = lcm(val,st[0][x + 1]);
	return x + 1;
}
int main()
{
	int T;
	init();
	cin>>T;
	while(T--)
	{
		cin>>n;lim = prime[n + 1];
		for(int i = 1;i <= lim;i++) vis[i] = 0;
		for(int i = 1;i <= n;i++) 
		{
			cin>>a[i];st[0][i] = a[i];
			if(st[0][i] > lim) st[0][i] = -1;
		}
		for(int i = 1;i <= 19;i++)
			for(int j = 1;j <= n;j++)
			{
				if((1 << i) + j - 1 > n) st[i][j] = -1;
				else st[i][j] = lcm(st[i - 1][j],st[i - 1][j + (1 << (i - 1))]);
			}
		for(int i = 1;i <= n;i++)
		{
			int tp = i,now = st[0][i];
			while(tp <= n && now != -1)
			{
				vis[now] = 1;
				tp = fd(tp,now);
			}
		}
		for(int i = 1;i <= lim;i++)
			if(!vis[i]) 
			{
				cout<<i<<endl;
				break;
			}
	}
	return 0;
}

F

咕。