Codeforces Round #738 (Div. 2) 题解

比赛地址:https://codeforces.com/contest/1559

补完题了。

A

可以证明,我们能把这个序列变成 \([x,x,\cdots,x]\),其中 \(x=\bigvee_{i=1}^n a_i\)。所以答案就是 \(\bigvee_{i=1}^n a_i\)

using ll=long long;

void mian(){
	int n;scanf("%d",&n);
	std::vector<int> a(n);
	for(auto &i:a)scanf("%d",&i);
	int ans=a[0];
	for(auto i:a)ans&=i;
	printf("%d\n",ans);
}

B

随便贪心。

using ll=long long;

const int N=100;

int n;
char s[N+10];

void mian(){
	scanf("%d%s",&n,s+1);
	int fst=-1;
	for(int i=1;i<=n;i++)
		if(s[i]!='?'){fst=i;break;}
	if(fst==-1){
		for(int i=1;i<=n;i++){
			if(i&1)s[i]='R';
			else s[i]='B';
		}
		puts(s+1);
	}
	else{
		// 22 行的意思是如果 s[i+1] = 'R',则 s[i] = 'B',反之同理。
		for(int i=fst-1;i>=1;i--)if(s[i]=='?')s[i]=s[i+1]^'R'^'B';
		for(int i=fst+1;i<=n;i++)if(s[i]=='?')s[i]=s[i-1]^'R'^'B';
		puts(s+1);
	}
}

C

只可能有 3 种方案:

  1. \(1\to 2\to\cdots\to n\to n+1\)
  2. \(n+1\to 1\to 2\to\cdots\to n\)
  3. 我们找到一个 \(p\),使得 \(a_p=0\)\(a_{p+1}=1\),那么方案是 \(1\to 2\to\cdots\to p\to n+1\to p+1\to\cdots\to n\)
using ll=long long;

const int N=1e4;

int n,a[N+10];

void mian(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",a+i);
	if(a[1]==1){
		printf("%d ",n+1);
		for(int i=1;i<=n;i++)
			printf("%d%c",i," \n"[i==n]);
		return;
	}
	if(a[n]==0){
		for(int i=1;i<=n+1;i++)
			printf("%d%c",i," \n"[i==n+1]);
		return;
	}
	int p=-1;
	for(int i=1;i<n;i++)
		if(a[i]==0&&a[i+1]==1){p=i;break;}
	if(p==-1)puts("-1");
	else{
		for(int i=1;i<=p;i++)
			printf("%d ",i);
		printf("%d ",n+1);
		for(int i=p+1;i<=n;i++)
			printf("%d%c",i," \n"[i==n]);
	}
}

D1

我们来证明操作完后一定有一个森林变成树,不妨记那个森林为 \(B\),另外那个为 \(A\)

考虑 \(A\) 中任意一个点对 \((u,v)\),如果它们在 \(A\) 中不连通,那么它们在 \(B\) 中一定连通,否则就可以把 \((u,v)\) 连上。于是 \(B\) 就变成一棵树了。

贪心。遍历一遍 \((i,j)\)\(1\le i\lt j\le n\)),能加就加。

using ll=long long;

const int N=1000;

int n,m1,m2;
int ans1[N+10],ans2[N+10];

struct DSU{
	int fa[N+10],n;
	DSU(int _n=0){init(_n);}
	inline void init(int _n){n=_n;for(int i=1;i<=_n;i++)fa[i]=i;}
	int getRt(int u){return fa[u]==u?u:fa[u]=getRt(fa[u]);}
	inline bool same(int u,int v){return getRt(u)==getRt(v);}
	inline void merge(int u,int v){
		int fu=getRt(u),fv=getRt(v);
		if(fu!=fv)fa[fu]=fv;
	}
}d1,d2;

void mian(){
	scanf("%d%d%d",&n,&m1,&m2);
	d1=DSU(n),d2=DSU(n);
	for(int i=1;i<=m1;i++){
		int u,v;scanf("%d%d",&u,&v);
		d1.merge(u,v);
	}
	for(int i=1;i<=m2;i++){
		int u,v;scanf("%d%d",&u,&v);
		d2.merge(u,v);
	}
	int tot=0;
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++){
			if((!d1.same(i,j))&&(!d2.same(i,j))){
				ans1[++tot]=i,ans2[tot]=j;
				d1.merge(i,j);
				d2.merge(i,j);
			}
		}
	printf("%d\n",tot);
	for(int i=1;i<=tot;i++)
		printf("%d %d\n",ans1[i],ans2[i]);
}

D2

考虑如何优化这个贪心。

先把所有点往 \(1\) 连边,能连就连。然后我们考虑 \(A\) 中不与 \(1\) 连通的点集 \(L\)\(B\) 中不与 \(1\) 连通的点集 \(R\)。那么 \(L\cap R=\varnothing\),且任取 \(l\in L,r\in R\),在图 \(A\)\(l\)\(1\) 不连通,\(r\)\(1\) 连通,\(B\) 中相反。于是任意的 \(l,r\) 都能连边,配个对即可。

using ll=long long;

const int N=1e5;

int n,m1,m2,ans1[N+10],ans2[N+10],a[N+10],b[N+10],cnta,cntb,tota;

struct DSU{
	int fa[N+10],n;
	DSU(int _n=0){init(_n);}
	inline void init(int _n){n=_n;for(int i=1;i<=_n;i++)fa[i]=i;}
	int getRt(int u){return fa[u]==u?u:fa[u]=getRt(fa[u]);}
	inline bool same(int u,int v){return getRt(u)==getRt(v);}
	inline void merge(int u,int v){
		int fu=getRt(u),fv=getRt(v);
		if(fu!=fv)fa[fu]=fv;
	}
}d1,d2;

void mian(){
	scanf("%d%d%d",&n,&m1,&m2);
	d1.init(n),d2.init(n);
	for(int i=1;i<=m1;i++){
		int u,v;scanf("%d%d",&u,&v);
		d1.merge(u,v);
	}
	for(int i=1;i<=m2;i++){
		int u,v;scanf("%d%d",&u,&v);
		d2.merge(u,v);
	}
	for(int i=2;i<=n;i++)
		if((!d1.same(1,i))&&(!d2.same(1,i))){
			ans1[++tota]=1;
			ans2[tota]=i;
			d1.merge(1,i),d2.merge(1,i);
		}
	for(int i=1;i<=n;i++)
		if(!d1.same(1,i))a[++cnta]=i,d1.merge(1,i);
	for(int i=1;i<=n;i++)
		if(!d2.same(1,i))b[++cntb]=i,d2.merge(1,i);
	for(int i=1;i<=std::min(cnta,cntb);i++)
		ans1[++tota]=a[i],ans2[tota]=b[i];
	printf("%d\n",tota);
	for(int i=1;i<=tota;i++)
		printf("%d %d\n",ans1[i],ans2[i]);
}

E

先不考虑 \(\gcd\) 的限制。此时这就是一个背包 dp,可以用前缀和优化。这里不赘述了。

\(f(a_1,a_2,\cdots,a_n)\) 表示 \(a\) 是否满足前两个条件,现在考虑 \(\gcd\) 的那个条件,答案就是:

\[\begin{aligned} \sum_{a_1=l_1}^{r_1}\sum_{a_2=l_2}^{r_2}\cdots\sum_{a_n=l_n}^{r_n}[\gcd(a_1,a_2,\cdots,a_n)=1]f(a_1,a_2,\cdots,a_n)&=\sum_{a_1=l_1}^{r_1}\sum_{a_2=l_2}^{r_2}\cdots\sum_{a_n=l_n}^{r_n}f(a_1,a_2,\cdots,a_n)\sum_{d\mid\gcd(a_1,a_2,\cdots,a_n)}\mu(d)\\ &=\sum_{d=1}^m\mu(d)\sum_{a_1=\left\lceil\frac{l_1}{d}\right\rceil}^{\left\lfloor\frac{r_1}{d}\right\rfloor}\sum_{a_2=\left\lceil\frac{l_2}{d}\right\rceil}^{\left\lfloor\frac{r_2}{d}\right\rfloor}\cdots\sum_{a_n=\left\lceil\frac{l_n}{d}\right\rceil}^{\left\lfloor\frac{r_n}{d}\right\rfloor}f(a_1d,a_2d,\cdots,a_nd) \end{aligned} \]

根据上式可知每次计算 \(\sum f\) 时只需枚举到 \(\frac md\)

时间复杂度 \(\mathcal O\left(n\sum_{d=1}^m\frac md\right)=\mathcal O(nm\ln m)\)

using ll=long long;

const int N=50;
const int M=1e5;
const int P=998244353;

int n,m,l[N+10],r[N+10],f[M+10],sum[M+10];
int prm[M+10],totp,notPrm[M+10],mu[M+10];

void init(){
	mu[1]=1;notPrm[1]=1;
	for(int i=2;i<=M;i++){
		if(!notPrm[i]){prm[++totp]=i;mu[i]=-1;}
		for(int j=1;j<=totp&&i*prm[j]<=M;j++){
			notPrm[i*prm[j]]=1;
			if(i%prm[j]==0){mu[i*prm[j]]=0;break;}
			else mu[i*prm[j]]=mu[i]*mu[prm[j]];
		}
	}
}

int calc(int x){
	int M=m/x;
	for(int j=0;j<=M;j++)
		f[j]=sum[j]=0;
	f[0]=1;
	for(int j=0;j<=M;j++)
		sum[j]=1;
	for(int i=1;i<=n;i++){
		int L=(l[i]+x-1)/x,R=r[i]/x;
		if(L>R)return 0;
		for(int j=0;j<=M;j++){
			int sumL=j-L>=0?sum[j-L]:0;
			int sumR=j-R-1>=0?sum[j-R-1]:0;
			f[j]=(sumL-sumR+P)%P;
		}
		sum[0]=f[0];
		for(int j=1;j<=M;j++)
			sum[j]=(sum[j-1]+f[j])%P;
	}
	return sum[M];
}

void mian(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d%d",l+i,r+i);
	int ans=0;
	for(int i=1;i<=m;i++)
		ans+=1LL*mu[i]*calc(i)%P,ans%=P;
	printf("%d\n",(ans+P)%P);
}
posted @ 2021-08-18 23:53  registerGen  阅读(19)  评论(0编辑  收藏  举报