Codeforces Global Round 24

Preface

最近可能中了降智病毒,写什么都写不过

下午打的校趣味赛看错题一个爆搜WA了四次,少罚一次时都Rank1了

然后晚上CF先是C想半天,然后D作为曾经最擅长的计数题也漏想了一种状态可能性,对着\(O(n)\)的假复杂度算法看半天

懂不懂什么叫六连掉的含金量啊,不过值得一提的是今天找到了一个以前WC拿Ag的爷预组队,我已经准备好被带飞了


A. Doremy's Paint

SB题,不难发现区间往外扩展一定不会让答案变劣,因此直接输出1 n即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N];
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]);
		printf("1 %d\n",n);
	}
	return 0;
}

B. Doremy's Perfect Math Class

不难发现辗转相减的过程得到的数不会小于所有数的\(\gcd\),设为\(g\)

因此\(g\)一定是最小的划分单位,能表出的数一定是\(g\)的倍数,故答案就是\(\frac{a_n}{g}\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N],g;
inline int gcd(CI n,CI m)
{
	return m?gcd(m,n%m):n;
}
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]);
		for (g=a[1],i=2;i<=n;++i) g=gcd(g,a[i]);
		printf("%d\n",a[n]/g);
	}
	return 0;
}

C. Doremy's City Construction

刚开始其实早就想到构造方法了,不过被相等的中间段搞了,跌跌撞撞才过

首先假设所有数都不相同,则我们可以把所有数排序后,选一个断点把所有数分成两个集合

不难发现只要两个集合之间相互连边一定是满足题意的,故偶数时答案最大为\((\frac{n}{2})^2\),奇数时最大为\((\frac{n-1}{2}\times \frac{n+1}{2})\)

考虑如果存在相同的数,我们就不能粗暴地在中间位置进行划分了,因为如果相同的数被分在了两个集合里就一定不合法

那么我们发现所有的合法端点就是每一段数的分界点,直接枚举取最大的一个即可

注意特判所有数都相同的情况,答案为\(\lfloor\frac{n}{2}\rfloor\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1000005;
int t,n,a[N];
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]);
		long long ans=0; for (sort(a+1,a+n+1),i=1;i<n;++i)
		if (a[i]!=a[i+1]) ans=max(ans,1LL*i*(n-i));
		printf("%lld\n",max(ans,1LL*n/2));
	}
	return 0;
}

D. Doremy's Pegging Game

首先我们要转化出一个碰到蓝点的充要条件,设\(t=\lfloor\frac{n}{2}\rfloor\)

则当局面中第一次出现大于等于\(t\)个被移除的红点时则碰到了蓝点

刚开始我就naive地认为停止的局面一定是恰好有\(t\)个被移除的红点,因此一直卡着

那么我们考虑枚举停止时被移除的红点的个数\(i(t\le i\le n-2+[n\text{ is even}])\)

我们发现此时两个端点是不能被移除的,还剩下另一侧有\(n-2-i\)个点,枚举其中事前被移除的点个数\(j\)

考虑最后一次移除一定造就了连续\(i\)个被移除点的局面,因此方案数为\(2t-i\),然后乘上另一边选要删除的点的方案数\(C_{n-2-i}^{j}\)

由于前\(i+j-1\)个点的顺序不变要乘上\((i+j-1)!\),且最后要记得乘上环上选起始位置的方案数\(n\)

\[\begin{aligned} & n\times \sum_{i=t}^{n-2+[n\text{ is even}]} \sum_{j=0}^{n-2-i}C_{n-2-i}^{j}\times (i+j-1)!\times(2t-i) \end{aligned} \]

复杂度\(O(n^2)\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=5005;
int n,mod,fac[N],ifac[N],pw[N],ans;
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline int C(CI n,CI m)
{
	if (n<0||m<0||m>n) return 0;
	return 1LL*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
inline void init(CI n)
{
	RI i; for (fac[0]=i=1;i<=n;++i) fac[i]=1LL*fac[i-1]*i%mod;
	for (ifac[n]=quick_pow(fac[n]),i=n-1;~i;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
	for (pw[0]=i=1;i<=n;++i) pw[i]=pw[i-1],inc(pw[i],pw[i-1]);
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (scanf("%d%d",&n,&mod),init(n),i=n/2;i<=n-2;++i)
	for (j=0;j<=n-i-2;++j) inc(ans,1LL*C(n-i-2,j)*(n/2*2-i)%mod*fac[i+j-1]%mod);
	if (!(n&1)) inc(ans,fac[n-2]); return printf("%d",1LL*ans*n%mod),0;
}

E. Doremy's Number Line

捏麻麻的刚开始看错题了想了1h做不来,后来发现我以为所有点的染色都染颜色\(1\),没想到是染编号\(i\)

那么正确理解题意后我们可以先判掉一些显然的情况,比如\(a_1\ge k\)\(a_1+b_1< k\),以及\(n=1\)的情形

不能发现若一个点\(x\)可以被染色,则所有小于它的位置也能被染色

于是下面我们就考虑怎么用\(2\sim n\)操作得到一个有颜色的最远的位置\(mx\)

不妨考虑把后面的操作按\(a_i\)排序,我们考虑记\(f_i\)表示做完前\(i\)个操作后,有颜色的最远的位置是哪里

考虑转移,常规转移的有三种:

  • 不用操作\(i\),则\(f_i=f_{i-1}\)

  • 直接染\(a_i\),则\(f_i=\max(f_i,a_i)\)

  • 用第二种方法染,则\(f_i=\max(f_i,\min(f_{i-1},a_i)+b_i)\)

但是还有一种很隐蔽的转移,由于我们这里的\(a_i\)升序,因此我们可以考虑先用\(i\)操作把\(a_{i-1}\)染了,然后用\(i-1\)操作把\(a_{i-1}+b_{i-1}\)染了,则有转移:

  • \(f_i=\max(f_i,a_{i-1}+b_{i-1})\)

这种既像DP又像贪心的题说实话还是挺少见的

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
struct element
{
	int a,b;
	friend inline bool operator < (const element& A,const element& B)
	{
		return A.a!=B.a?A.a<B.a:A.b<B.b;
	}
}p[N]; int t,n,k,f[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d%d",&p[i].a,&p[i].b);
		if (p[1].a>=k) { puts("YES"); continue; }
		if (n==1||p[1].a+p[1].b<k) { puts("NO"); continue; }
		for (sort(p+2,p+n+1),f[2]=p[2].a,i=3;i<=n;++i)
		f[i]=max(max(p[i-1].a+p[i-1].b,p[i].a),max(f[i-1],min(f[i-1],p[i].a)+p[i].b));
		puts(min(f[n],p[1].a)+p[1].b>=k?"YES":"NO");
	}
	return 0;
}

F. Doremy's Experimental Tree

人类智慧题,让人不得不嗟叹出题人的思路

首先我们需要看出一个性质,如果一条两点\(x,y\)间的路径在另两个点\(X,Y\)间的路径上,则必有\(f(x,y)>f(X,Y)\)

证明是显然的,因为这样环上的点变多了,其它点到环的距离也变短了

因此我们发现如果我们构建一个图,图上两点\(i,j\)间的边权是\(f(i,j)\),则这个图的最大生成树拥有和原树一样的结构

这个的证明利用上面的性质还是很好感性理解的,具体严谨的证明我不太会,官方的Tutorial也没讲

那么接下来就考虑如何计算每条边的边权,我们考虑对于一条以\(x\)为父节点,\(y\)为子节点的树边

不难发现它的边权就是\(\frac{f(x,x)-f(x,y)}{size_y}\),这个画个图是很obvious的

#include<cstdio>
#include<iostream>
#include<vector>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=2005;
int n,w[N][N],dis[N],sz[N]; bool vis[N]; vector <int> v[N];
inline void DFS(CI now=1,CI fa=0)
{
	sz[now]=1; for (int to:v[now]) if (to!=fa) DFS(to,now),sz[now]+=sz[to];
	for (int to:v[now]) if (to!=fa) printf("%lld %lld %lld\n",now,to,(w[now][now]-w[now][to])/sz[to]);
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (scanf("%lld",&n),i=1;i<=n;++i)
	for (j=1;j<=i;++j) scanf("%lld",&w[i][j]),w[j][i]=w[i][j];
	for (i=1;i<=n;++i)
	{
		int pos=0; for (j=1;j<=n;++j)
		if (!vis[j]&&(!pos||dis[j]>dis[pos])) pos=j;
		if (i!=1) for (j=1;j<=n;++j)
		if (w[pos][j]==dis[pos]&&vis[j]) v[pos].push_back(j),v[j].push_back(pos);
		for (vis[pos]=1,j=1;j<=n;++j) dis[j]=max(dis[j],w[pos][j]);
	}
	return DFS(),0;
}

G3. Doremy's Perfect DS Class (Hard Version)

嘛虽然当时只准备看下G1怎么做的,没想到到G2都挺好理解,顺便看了下发现G3的3300题也不是绝对不可做的,不过我是肯定想不到这么妙的方法啦

首先考虑\(n\)为奇数的做法,我们发现此时如果将所有数除以\(2\)下取整,只有\(1\)会得到和其它数不同的值

于是就有一个巧妙的做法,我们在所有除以\(2\)下取整相同的数之间连一条边,这样只要找出没有和其它点连边的那个点在哪里即可

我们考虑设\(C(l,r)\)表示一个区间\([l,r]\)中所有点之间的连边数,不难发现\(C(l,r)=r-l+1-Q(l,r,2)\)

这样我们可以考虑用分治法来解决问题,我们考虑判断\(1\)\([1,m]\)还是\([m+1,n]\)这个区间里

考虑设\(x,y\)\([1,m],[m+1,n]\)区间中没有连边的点数目,则\(x=m-2\times C(1,m),y=n-m-2\times C(m+1,n)\)

那么剩下的边一定在左边的\(x\)和右边的\(y\)个点之间连,除了没有边的\(1\),其余的点之间肯定是一一对应连边的

所以若\(x=y+1\)则说明\(1\)\([1,m]\)里,反之若\(y=x+1\)则说明\(1\)\([m+1,n]\)里,这样我们可以用\(20\)次操作来完成奇数的情况

接下来考虑当\(n\)为偶数的情况,此时\(1\)\(n\)都是没有边和其它点相连的,我们还是套用上面的分治法

\(x>y\),则\(x,y\)都在\([1,m]\)里,若\(x<y\),则\(x,y\)都在\([m+1,n]\)

而若\(x=y\),则说明\(x,y\)分别各属于一个区间,我们可以调用一次\(Q(1,m,n)\)\(Q(m+1,n,n)\)来确定\(n\)在哪个区间里

注意我们只需要一次操作就可以分出\(1\)\(n\),于是偶数的操作次数就是\(21\)

考虑这剩下的一次操作要怎么省下来,我们考虑在最后一个长度为\(2\)的可能区间上做文章

具体地,当我们把\(1\)的可能区间缩小到\([l,r]\)时,我们已经知道了\(C(1,r),C(1,l-1),C(l,n),C(r+1,n)\)的值

此时若\(l+1=r\),我们可以直接利用之前的信息做如下讨论:

  • \(C(1,l-1)+1=C(1,r)\),则我们只要看\(C(1,l)\)\(C(1,l-1)\)的关系即可确定\(l,l+1\)哪个是\(1\)
  • \(C(l,n)=C(r+1,n)+1\),则我们只要看\(C(r,n)\)\(C(r+1,n)\)的关系即可确定\(r-1,r\)哪个是\(1\)
  • 否则则说明\(1,n\)都在\([l,r]\)中,即要么\(p_l=1\and p_r=n\),要么\(p_l=n\and p_r=1\),此时直接调用一次\(Q\)操作即可

综上,我们减少了最后一个可能区间的操作次数,使得答案卡进\(20\)大关,可喜可贺

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
int n,dirt=-1;
inline int query(CI l,CI r,CI k)
{
	printf("? %d %d %d\n",l,r,k); fflush(stdout);
	int x; scanf("%d",&x); return x;
}
inline void print(CI pos)
{
	printf("! %d\n",pos); fflush(stdout);
}
inline void solve(CI l,CI r,CI l1,CI l2,CI r1,CI r2)
{
	if (l==r) return print(l);
	if (l+1==r)
	{
		if (r1==r2+1)
		{
			if (query(r,n,2)==r2+1) print(r); else print(l);
		} else if (l1==l2+1)
		{
			if (query(1,l,2)==l2+1) print(l); else print(r);
		} else
		{
			if (l>1)
			{
				if (query(1,l,n)==2) print(r); else print(l);
			} else
			{
				if (query(r,n,n)==2) print(l); else print(r);
			}
		}
		return;
	}
	int mid=l+r>>1,L=query(1,mid,2),R=query(mid+1,n,2);
	if (L*2-mid>R*2-(n-mid)) solve(l,mid,L,l2,r1,R);
	else if (L*2-mid<R*2-(n-mid)) solve(mid+1,r,l1,L,R,r2);
	else
	{
		if (~dirt)
		{
			if (dirt) solve(l,mid,L,l2,r1,R); else solve(mid+1,r,l1,L,R,r2);
		}
		if (query(1,mid,n)==2) dirt=0,solve(mid+1,r,l1,L,R,r2);
		else dirt=1,solve(l,mid,L,l2,r1,R);
	}
}
int main()
{
	return scanf("%d",&n),solve(1,n,n/2+1,0,n/2+1,0),0;
}

Postscript

我真的搞不懂为什么现在比之前刚康复的时候还菜……

posted @ 2022-11-28 21:35  空気力学の詩  阅读(203)  评论(9编辑  收藏  举报