Codeforces Round #830 (Div. 2)

Preface

AB很水,C一般难度,D1很简单但D2确实巧妙

没有现场打有点可惜,后面补的时候都是1A(结果每次比赛的时候都要莫名WA上好久)

UPD:E被\(\gcd\)的复杂度卡了好久,而且一个可以记忆化的地方没存导致TLE了好久,不过还是Rush出来了(虽然很惨烈地挂了一页)


A. Bestie

我刚开始没咋想,感觉操作步数不会很多,直接Rush了一个爆搜上去

其实只用看最后两个位置即可,因为\(\gcd(n,n-1)=1\),因此答案最多是\(3\)

#include<cstdio>
#include<vector>
#include<queue>
#define RI register int
#define CI const int&
#define mp make_pair
using namespace std;
typedef vector <int> VI;
const int N=25;
int t,n,a[N]; priority_queue < pair<int,VI> > hp;
inline int gcd(CI n,CI m)
{
	return m?gcd(m,n%m):n;
}
inline int calc(VI& v)
{
	int ret=v[0]; for (RI i=1;i<n;++i) ret=gcd(ret,v[i]); return ret;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
		while (!hp.empty()) hp.pop(); VI cur; cur.clear();
		for (i=1;i<=n;++i) cur.push_back(a[i]); hp.push(mp(0,cur));
		while (!hp.empty())
		{
			cur=hp.top().second; int ret=-hp.top().first,G=calc(cur); hp.pop();
			if (G==1) { printf("%d\n",ret); break; }
			for (i=1;i<=n;++i)
			{
				VI apd=cur; apd[i-1]=gcd(apd[i-1],i);
				int NG=calc(apd); if (NG<G) hp.push(mp(-(ret+n-i+1),apd));
			}
		}
	}
	return 0;
}

B. Ugu

首先简单观察我们发现,所有相邻的相同的数是可以合并的,我们每次操作都取这一颜色段的开头即可

那么现在问题就变成对于一个\(01\)间隔的串的问题了,直接从前往后贪心就能得到答案,稍作总结就能得到式子

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,sum; char s[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d%s",&n,s+1),sum=0,i=2;i<=n;++i)
		if (s[i]!=s[i-1]) ++sum; printf("%d\n",max(0,sum-(s[1]=='0')));
	}
	return 0;
}

C1. Sheikh (Easy version)

大概讲一下C1的做法吧(虽然我没写代码)

首先由于异或是不进位的加法,因此区间变大\(f(l,r)\)肯定是不会变小的,因此我们就要找出最小的区间使得其值等于\(f(1,n)\)

容易想到当一个端点固定时,另一个端点的取值具有可二分性,因此可以二分或者双指针来做


C2. Sheikh (Hard Version)

我们考虑在什么情况下一个区间的值是等于\(f(L,R)\)的,放在二进制下来看就是每一个二进制位上1的个数不能超过一个

\(10^9\)范围内最多只有\(30\)个二进制位,如果我们不考虑\(0\)的话,显然最多只能在\([L,R]\)的基础上拿走\(31\)个数

因此把\(0\)剔除掉直接从两端向内暴力找即可,复杂度\(O(q\log a_i)\)

#include<cstdio>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
typedef long long LL;
const int N=100005;
int t,n,q,a[N],L,R,pos[N],cnt,pxor[N]; LL psum[N];
inline LL calc(CI l,CI r)
{
	return psum[r]-psum[l-1]-(pxor[r]^pxor[l-1]);
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d%d",&n,&q),cnt=0,i=1;i<=n;++i)
		{
			if (scanf("%d",&a[i]),a[i]) pos[++cnt]=i;
			psum[i]=psum[i-1]+a[i]; pxor[i]=pxor[i-1]^a[i];
		}
		while (q--)
		{
			scanf("%d%d",&L,&R); LL tar=calc(L,R);
			int l=lower_bound(pos+1,pos+cnt+1,L)-pos;
			int r=upper_bound(pos+1,pos+cnt+1,R)-pos-1;
			int ansl=0,ansr=n+1; for (i=l;i<=r;++i)
			{
				if (calc(pos[i],pos[r])<tar) break;
				for (j=r;j>=i;--j)
				{
					if (calc(pos[i],pos[j])<tar) break;
					if (pos[j]-pos[i]+1<ansr-ansl+1) ansl=pos[i],ansr=pos[j];
				}
			}
			if (ansl) printf("%d %d\n",ansl,ansr); else printf("%d %d\n",L,L);
		}
	}
	return 0;
}

D1. Balance (Easy version)

还是讲个想法(因为也没写代码)

不难发现由于没有删除操作,因此对于同一个\(k\),它询问的结果是单调不减的

因此可以直接用一个\(lst_k\)记录下\(k\)的答案,下次直接从这个\(lst_k\)开始往后跳即可

由于每个\(k\)最多向后跳\(\frac{n}{k}\)个点,因此总复杂度是\(O(n\log n)\)的,加上map是两只\(\log\)


D2. Balance (Hard version)

考虑换一种统计mex的方式,对于一个数\(k\),我们令一个集合\(S_k=\{0,k,2k,3k,\cdots\}\)

然后每当一个数\(t\times k\)出现后,我们把这个数在\(S_k\)中删去,这样k-mex的值就是集合\(S_k\)中最小的元素了

那么这里我们一样,考虑统计出当一个数\(x\)被删去后,它会导致哪些\(k\)的k-mex发生改变,显然只要在跳\(lst_k\)的时候顺带维护下即可

对于每一个询问的\(k\),若\(S_k\)为空答案就是\(lst_k\),否则就是\(S_k\)中最小的元素

由于当一个数被丢进\(S_k\)时所有小于等于它的数都已经出现过了,因此我们不需要维护初始的满的\(S_k\),等有删除时直接插入即可

复杂度的话考虑均摊分析,大概就是D1的基础上加一个\(\log n\)的复杂度(这个复杂度是set的,算法本身的复杂度没变)

#include<cstdio>
#include<map>
#include<set>
#include<vector>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
map <int,int> vis,lst; map <int,set <int>> del; map <int,vector <int>> rmv;
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	int q,x; for (scanf("%lld",&q);q;--q)
	{
		char ch; while (ch=getchar(),ch!='+'&&ch!='-'&&ch!='?');
		scanf("%lld",&x); switch (ch)
		{
			case '+':
				vis[x]=1; for (int y:rmv[x]) del[y].erase(x); break;
			case '-':
				vis[x]=0; for (int y:rmv[x]) del[y].insert(x); break;
			case '?':
				if (!lst.count(x)) lst[x]=x;
				if (!del[x].empty()) printf("%lld\n",*del[x].begin()); else
				{
					while (vis[lst[x]])	rmv[lst[x]].push_back(x),lst[x]+=x;
					printf("%lld\n",lst[x]);
				}
				break;
		}
	}
	return 0;
}

E. Location

该发的牢骚前面都发完了,直接进入正题

以下复杂度讨论时假设\(n,q,\max a_i\)同阶,为方便均记为\(O(n)\)

首先看到这个数据范围和赋值操作很容易想到曾经我最拿手的分块(死去的板刷ynoi的记忆开始攻击我)

考虑设块大小为\(S\),修改操作不用说散块直接暴力搞就好了,复杂度是\(O(S\log a_i)\)(因为要暴力算\(\gcd\)),整块直接打个tag就好

那么考虑询问,散块显然也是直接暴力,复杂度是\(O(S\log a_i)\),现在考虑怎么算整块的答案

设这一块的值均为\(x\),考虑\(\frac{\operatorname{lcm}(x, b_i)}{\gcd(x, b_i)}=\frac{x\times b_i}{\gcd^2(x, b_i)}\),经典的处理方法是枚举\(y|x\)作为\(\gcd(x,b_i)\)的值

那么考虑找出所有\(y|b_i\)\(\min b_i\)即可,虽然不一定满足\(y\)\(\gcd(x,b_i)\),但是这样只会导致答案偏大,而正确的答案一定可以这样算出来,因此正确性是有保证的

由于块内的复杂度是\(O(\log a_i)\)的,再加一个记忆化可以保证整块的总复杂度为\(O(\frac{n}{S}\log a_i)\)

综上显然取\(S=\sqrt n\)即可得到\(O(n^{\frac{3}{2}}\cdot\log a_i)\)的算法

但由于我处理的时候太懒了老是暴力重构答案导致常数巨大,因此需要用\(O(1)\gcd\)的科技才能过

#include<cstdio>
#include<vector>
#include<iostream>
#include<cmath>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=50005;
int n,q,a[N],b[N],opt,l,r,x,blk[N],tag[N],mi[250][N],S;
vector <int> frac[N]; long long ans[N],rst[250][N];
namespace FG //Fast Gcd
{
	const int N=50005,S=250;
	int prm[N],cnt,G[S+5][S+5],frac[N][3]; bool vis[N];
	inline void init(CI n)
	{
		RI i,j; for (i=0;i<3;++i) frac[1][i]=1;
		for (i=2;i<=n;++i)
		{
			if (!vis[i]) for (prm[++cnt]=frac[i][2]=i,j=0;j<2;++j) frac[i][j]=1;
			for (j=1;j<=cnt&&i*prm[j]<=n;++j)
			{
				vis[i*prm[j]]=1; for (RI k=0;k<3;++k) frac[i*prm[j]][k]=frac[i][k];
				frac[i*prm[j]][0]*=prm[j]; sort(frac[i*prm[j]],frac[i*prm[j]]+3);
				if (i%prm[j]==0) break;
			}
		}
		for (i=1;i<=S;++i) G[i][0]=G[0][i]=i;
		for (i=1;i<=S;++i) for (j=1;j<=i;++j) G[i][j]=G[j][i]=G[i%j][j];
	}
	inline int gcd(int x,int y,int ret=1)
	{
		for (RI i=0;i<3;++i)
		{
			int cur; if (frac[x][i]>S)
			{
				if (y%frac[x][i]) cur=1; else cur=frac[x][i];
			} else cur=G[frac[x][i]][y%frac[x][i]];
			y/=cur; ret*=cur;
		}
		return ret;
	}
};
inline long long calc(CI x,CI y)
{
	int t=FG::gcd(x,y); return 1LL*x*y/(1LL*t*t);
}
inline void rebuild(CI id)
{
	if (!tag[id]) return; int lim=min(n,id*S);
	for (RI i=(id-1)*S+1;i<=lim;++i) a[i]=tag[id]; tag[id]=0;
}
inline void recalc(CI id)
{
	ans[id]=1e18; int lim=min(n,id*S);
	for (RI i=(id-1)*S+1;i<=lim;++i) ans[id]=min(ans[id],calc(a[i],b[i]));
}
inline long long count(CI id,long long ret=1e18)
{
	if (rst[id][tag[id]]) return rst[id][tag[id]];
	for (int y:frac[tag[id]]) if (mi[id][y]!=1e9) ret=min(ret,1LL*mi[id][y]*tag[id]/(1LL*y*y));
	return rst[id][tag[id]]=ret;
}
inline void modify(CI l,CI r,CI x)
{
	RI i; if (blk[l]==blk[r]) { for (rebuild(blk[l]),i=l;i<=r;++i) a[i]=x; recalc(blk[l]); return; }
	for (rebuild(blk[l]),i=l;i<=blk[l]*S;++i) a[i]=x; recalc(blk[l]);
	for (rebuild(blk[r]),i=(blk[r]-1)*S+1;i<=r;++i) a[i]=x; recalc(blk[r]);
	for (i=blk[l]+1;i<blk[r];++i) tag[i]=x,ans[i]=count(i);
}
inline long long query(CI l,CI r,long long ret=1e18)
{
	RI i; if (blk[l]==blk[r])
	{
		for (rebuild(blk[l]),i=l;i<=r;++i) ret=min(ret,calc(a[i],b[i])); return ret;
	}
	for (rebuild(blk[l]),i=l;i<=blk[l]*S;++i) ret=min(ret,calc(a[i],b[i]));
	for (rebuild(blk[r]),i=(blk[r]-1)*S+1;i<=r;++i) ret=min(ret,calc(a[i],b[i]));
	for (i=blk[l]+1;i<blk[r];++i) ret=min(ret,ans[i]);
	return ret;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (scanf("%d%d",&n,&q),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (S=(int)sqrt(n),i=1;i<=n;++i) scanf("%d",&b[i]),blk[i]=(i-1)/S+1;
	for (FG::init(50000),i=1;i<=50000;++i) for (j=i;j<=50000;j+=i) frac[j].push_back(i);
	for (i=1;i<=blk[n];++i) for (j=1;j<=50000;++j) mi[i][j]=1e9;
	for (i=1;i<=n;++i) for (int y:frac[b[i]]) mi[blk[i]][y]=min(mi[blk[i]][y],b[i]);
	for (i=1;i<=blk[n];++i) recalc(i); for (i=1;i<=q;++i)
	if (scanf("%d%d%d",&opt,&l,&r),opt==2) printf("%lld\n",query(l,r)); else scanf("%d",&x),modify(l,r,x);
	return 0;
}

Postscript

最近写题很少啊,有点怠惰了,需要好好反省一下(主要是在开樱之诗的坑,扶他自我的超人!)

posted @ 2022-10-28 20:04  空気力学の詩  阅读(83)  评论(0编辑  收藏  举报