Codeforces Round 864 (Div. 2)

发布时间 2023-04-12 15:59:00作者: 空気力学の詩

Preface

周六的最后一战,感觉脑子确实有点混沌了一个C题好多细节没考虑好心态差点爆炸

最后还好20min速切了D稳了手排名,不然已经回到蓝名了

感觉最近打比赛老是犯一些很离谱的失误,题目老是想不清楚导致好几场没法在比赛时写掉Div2的E了

嘛说白了还是练得少了,多刷题解决一切问题的说


A. Li Hua and Maze

不难发现如果这两个点中有一个在角落上,那么只要堵住它临近的两个格子即可

否则如果有一个在边上,那么只要堵住它临近的三个格子即可

否则我们肯定可以用四个格子堵住其中任意一个

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t,n,m,X1,Y1,X2,Y2;
inline bool corner(CI x,CI y)
{
	return (x==1&&y==1)||(x==1&&y==m)||(x==n&&y==1)||(x==n&&y==m);
}
inline bool edge(CI x,CI y)
{
	return x==1||x==n||y==1||y==m;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		scanf("%d%d%d%d%d%d",&n,&m,&X1,&Y1,&X2,&Y2);
		if (corner(X1,Y1)||corner(X2,Y2)) puts("2"); else
		if (edge(X1,Y1)||edge(X2,Y2)) puts("3"); else puts("4");
	}
	return 0;
}

B. Li Hua and Pattern

首先把所有翻转后对应的位置上的颜色比较一下,如果不一样就至少要花费一次操作

然后都执行完后看下如果\(k<0\)则显然无解,否则要满足\(2|k\)才能通过修改某对位置来满足要求

但是还要注意一个情况,当\(n\)为奇数时\(k\)为奇数也是合法的,因为我们可以把多余的操作全部扔在最中间的那个点上,刚开始没考虑到还WA了一发

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int t,n,k,a[N][N];
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,&k),i=1;i<=n;++i)
		for (j=1;j<=n;++j) scanf("%d",&a[i][j]);
		for (i=1;i<=n;++i) for (j=1;j<=n;++j)
		if (a[i][j]!=a[n-i+1][n-j+1]) a[i][j]^=1,--k;
		if (k<0) { puts("NO"); continue; }
		if (k%2==0) { puts("YES"); continue; }
		if (n&1) puts("YES"); else puts("NO");
	}
	return 0;
}

C. Li Hua and Chess

思路很简单,但是我就是猪脑过载想不清楚,前前后后卡了快1h真是菜得飞起

一个很naive的想法就是我们尽量询问角落上的点,这样每次询问就能确定目标点在两条线段上

通过连续的两次询问可以很容易地把答案锁定在某个线段上,然后这样在第三次询问时我们可以询问线段的一个端点来确定位置了

说起来轻巧但是要注意很多Corner Cases,比如考虑\(n,m\)的大小关系,相交的形态等等,具体看代码吧

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t,n,m;
inline int ask(CI x,CI y)
{
	printf("? %d %d\n",x,y); fflush(stdout);
	int z; scanf("%d",&z); return z;
}
inline void answer(CI x,CI y)
{
	printf("! %d %d\n",x,y); fflush(stdout);
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		scanf("%d%d",&n,&m); int A,B,C;
		if (n>m)
		{
			A=ask(1,1); B=ask(n,1);
			if (1+A!=n-B)
			{
				if (A<B) answer(n-B,1+A); else if (A>B) answer(1+A,1+B); else
				{
					C=ask(n-B,1+A); answer(n-B+C,1+A);
				}
			} else
			{
				C=ask(1+A,1); answer(1+A,1+C);
			}
		} else
		{
			A=ask(1,1); B=ask(1,m);
			if (1+A!=m-B) 
			{
				if (A<B) answer(1+A,m-B); else if (A>B) answer(1+B,1+A); else
				{
					C=ask(1+A,m-B); answer(1+A,m-B+C);
				}
			} else
			{
				C=ask(1,1+A); answer(1+C,1+A);
			}
		}
	}
	return 0;
}

D. Li Hua and Tree

早知道先写D题了,就是个模拟+STL的裸题

首先我们很容易想到旋转操作只会影响当前点、当前点的重儿子、当前点的父亲这三个点的信息

很显然可以直接用set维护下每个点的重儿子信息,然后手玩一下转移过程别搞错细节就行

#include<cstdio>
#include<iostream>
#include<set>
#include<vector>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
struct Data
{
	int s,id;
	inline Data(CI S=0,CI ID=0)
	{
		s=S; id=ID;
	}
	friend inline bool operator < (const Data& A,const Data& B)
	{
		return A.s!=B.s?A.s>B.s:A.id<B.id;
	}
}; multiset <Data> hp[N]; int n,m,a[N],sz[N],sum[N],fa[N],x,y; vector <int> v[N];
inline void DFS(CI now=1,CI f=0)
{
	fa[now]=f; sum[now]=a[now]; sz[now]=1;
	for (int to:v[now]) if (to!=f)
	{
		DFS(to,now); sum[now]+=sum[to]; sz[now]+=sz[to];
		hp[now].insert(Data(sz[to],to));
	}
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%lld%lld",&n,&m),i=1;i<=n;++i) scanf("%lld",&a[i]);
	for (i=1;i<n;++i) scanf("%lld%lld",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for (DFS(),i=1;i<=m;++i)
	{
		if (scanf("%lld%lld",&x,&y),x==1) printf("%lld\n",sum[y]); else
		{
			if (hp[y].empty()) continue;
			int son=hp[y].begin()->id; hp[y].erase(hp[y].begin());
			hp[fa[y]].erase(hp[fa[y]].find(Data(sz[y],y)));
			hp[son].insert(Data(sz[y]-sz[son],y));
			hp[fa[y]].insert(Data(sz[y],son));
			int tf=fa[y]; fa[y]=son; fa[son]=tf;
			int ts=sz[y]; sz[y]=ts-sz[son]; sz[son]=ts;
			int A=sum[y],B=sum[son]; sum[y]=A-B; sum[son]=A;
		}
	}
	return 0;
}

E. Li Hua and Array

什么大缝合题,数论+树论+数据结构大综合了属于是,不过每个涉及得都很浅所以还是很好想的

首先很自然可以想到我们对于每个数\(x(x>1)\),连一条\(\phi(x)\to x\)的边,这样就形成了一颗树

然后询问就可以转化为求一个区间内所有点到它们的公共LCA的距离之和

很容易想到我们求出每个点的深度后就转化为区间求和和区间求LCA的问题了,但区间所有点的LCA乍一看感觉有点难处理的说

其实方法也很简单,我们找出区间内DFS序最小和最大的点,则它们的LCA就是区间内所有点的LCA,这个画个图看一下还是很好理解的说

然后考虑修改,这个就是经典套路了,由于取\(\phi\)\(\log\)次后就会变成\(1\),所以我们可以直接暴力搞,势能分析一下复杂度是对的

当然这个性质还给我们带来一个好处,由于树高是\(\log\)级别的,所以我们不需要在写求LCA的算法了,直接暴力跳即可

总复杂度\(O(n\log A_i)\)

#include<cstdio>
#include<iostream>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,M=5000000;
int n,m,a[N],tp,l,r,pri[M+5],cnt,phi[M+5],seq[M+5],dfn[M+5],dep[M+5],idx;
bool vis[M+5]; set <int> s[M+5];
inline void init(CI n)
{
	phi[1]=1; for (RI i=2,j;i<=n;++i)
	{
		if (!vis[i]) pri[++cnt]=i,phi[i]=i-1;
		for (j=1;j<=cnt&&i*pri[j]<=n;++j)
		if (vis[i*pri[j]]=1,i%pri[j]) phi[i*pri[j]]=phi[i]*(pri[j]-1);
		else { phi[i*pri[j]]=phi[i]*pri[j]; break; }
	}
}
inline void DFS(CI now=1)
{
	seq[dfn[now]=++idx]=now; dep[now]=dep[phi[now]]+1; for (int to:s[now]) DFS(to);
}
inline int getLCA(int x,int y)
{
	if (dep[x]<dep[y]) swap(x,y);
	while (dep[x]>dep[y]) x=phi[x];
	if (x==y) return x;
	while (phi[x]!=phi[y]) x=phi[x],y=phi[y];
	return phi[x];
}
class Segment_Tree
{
	private:
		struct segment
		{
			long long sum; int mi,mx;
			inline segment(const long long SUM=0,CI MI=1e9,CI MX=0)
			{
				sum=SUM; mi=MI; mx=MX;
			}
		}node[N<<2];
		#define S(now) node[now].sum
		#define MI(now) node[now].mi
		#define MX(now) node[now].mx
		#define TN CI now=1,CI l=1,CI r=n
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void pushup(CI now)
		{
			S(now)=S(now<<1)+S(now<<1|1);
			MI(now)=min(MI(now<<1),MI(now<<1|1));
			MX(now)=max(MX(now<<1),MX(now<<1|1));
		}
	public:
		inline void build(TN)
		{
			if (l==r) return (void)(node[now]=(segment){dep[a[l]],dfn[a[l]],dfn[a[l]]});
			int mid=l+r>>1; build(LS); build(RS); pushup(now);
		}
		inline void modify(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end)
			{
				if (MX(now)==1) return;
				if (l==r) return (void)(a[l]=phi[a[l]],node[now]=(segment){dep[a[l]],dfn[a[l]],dfn[a[l]]});
			}
			int mid=l+r>>1; if (beg<=mid) modify(beg,end,LS); if (end>mid) modify(beg,end,RS); pushup(now);
		}
		inline long long getsum(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return S(now); int mid=l+r>>1; long long ret=0;
			if (beg<=mid) ret+=getsum(beg,end,LS); if (end>mid) ret+=getsum(beg,end,RS); return ret;
		}
		inline int getmin(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return MI(now); int mid=l+r>>1,ret=1e9;
			if (beg<=mid) ret=min(ret,getmin(beg,end,LS)); if (end>mid) ret=min(ret,getmin(beg,end,RS)); return ret;
		}
		inline int getmax(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return MX(now); int mid=l+r>>1,ret=0;
			if (beg<=mid) ret=max(ret,getmax(beg,end,LS)); if (end>mid) ret=max(ret,getmax(beg,end,RS)); return ret;
		}
		#undef S
		#undef MI
		#undef MX
		#undef TN
		#undef LS
		#undef RS
}SEG;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d%d",&n,&m),init(M),i=1;i<=n;++i)
	{
		scanf("%d",&a[i]); int tmp=a[i];
		while (tmp>1) s[phi[tmp]].insert(tmp),tmp=phi[tmp];
	}
	for (DFS(),SEG.build(),i=1;i<=m;++i)
	if (scanf("%d%d%d",&tp,&l,&r),tp==1) SEG.modify(l,r); else
	{
		int lca=getLCA(seq[SEG.getmin(l,r)],seq[SEG.getmax(l,r)]);
		printf("%lld\n",SEG.getsum(l,r)-1LL*dep[lca]*(r-l+1));
	}
	return 0;
}

Postscript

这场比赛是国人出的吧都是Li Hua的说,自从高中毕业后都以为再也不会见到了结果在CF上见到了