Codeforces Round #814 (Div. 2+Div. 1)

Preface

关于军训……它死了

第一次感觉到疫情的好处,就是不知道训了一半给不给学分,要不要补训

一直打隔膜颓废也不是办法,因此来写写题(才不是因为路由器没到舍不得用流量更新永劫无间呢)


A. Chip Game

看到这种可以用SG函数的题目先暴力艹上一发,发现当且仅当\(n,m\)奇偶性相同时为Tonya,反之

后来一想证明也很eazy,因为总的移动格子数是\(n+m-2\),每次的变化值都是奇数,因此等价于看\(n+m\)的奇偶性

#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
//const int N=105;
//int SG[N][N]; bool vis[N];
int t,n,m;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	/*RI i,j,k; for (i=1;i<=20;++i) for (j=1;j<=20;++j)
	{
		memset(vis,0,sizeof(vis));
		for (k=1;k<i;k+=2) vis[SG[i-k][j]]=1;
		for (k=1;k<j;k+=2) vis[SG[i][j-k]]=1;
		for (k=0;vis[k];++k); SG[i][j]=k;
	}
	for (i=1;i<=20;++i) for (j=1;j<=20;++j)
	printf("%d %d: %d\n",i,j,SG[i][j]);*/
	for (scanf("%d",&t);t;--t) scanf("%d%d",&n,&m),puts((n&1)==(m&1)?"Tonya":"Burenka");
	return 0;
}

B. Mathematical Circus

首先把\(k\)\(4\)取模,不难证明当\(k=0\)时是无解的

接下来考虑\(k=1/3\)的情况,容易发现只要分成一个奇数+一个偶数的情况即可

对于\(k=2\)的情况,我们每次考虑相邻的\(4\)个数\(\{4n+1,4n+2,4n+3,4n+4\}\),显然可以划分成\((4n+1,4n+4),(4n+2,4n+3)\)的形式,对于可能剩下的两个数\(\{4n+1,4n+2\}\)也分成\((4n+2,4n+1)\)即可

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
int t,n,k;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d",&t);t;--t)
	{
		scanf("%d%d",&n,&k); k%=4; if (!k) { puts("NO"); continue; }
		if (puts("YES"),k==2)
		{
			for (i=1;i+3<=n;i+=4) printf("%d %d\n%d %d\n",i,i+3,i+1,i+2);
			if (n%4==2) printf("%d %d\n",n,n-1);
		} else
		{
			for (i=1;i<=n;i+=2) printf("%d %d\n",i,i+1);
		}
	}
	return 0;
}

C. Fighting Tournament

不难发现一个关键性质,当最大的那个人来到最前面后其他人的胜利次数就不会发生变化了,这个人会一直赢下去

同时我们发现我们可以离线询问,这样就可以直接模拟比赛过程了,可以用deque,非常方便

#include<cstdio>
#include<algorithm>
#include<deque>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
struct ques
{
	int id,p,t;
	friend inline bool operator < (const ques& A,const ques& B)
	{
		return A.t<B.t;
	}
}q[N]; int t,n,m,a[N],c[N],pos,num[N],ans[N]; deque <int> dq;
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,&m),i=1;i<=n;++i)
		scanf("%d",&a[i]),c[i]=0,dq.push_back(a[i]),num[a[i]]=i;
		for (i=1;i<=m;++i) scanf("%d%d",&q[i].p,&q[i].t),q[i].id=i;
		RI now=1; for (sort(q+1,q+m+1),i=1;;++i)
		{
			int x=dq.front(); dq.pop_front(); int y=dq.front(); dq.pop_front();
			if (x<y) swap(x,y); ++c[num[x]]; dq.push_front(x); dq.push_back(y);
			while (now<=m&&q[now].t==i) ans[q[now].id]=c[q[now].p],++now;
			if (x==n) { pos=i; break; }
		}
		while (now<=m) ans[q[now].id]=c[q[now].p]+(q[now].p==num[n]?q[now].t-pos:0),++now;
		for (i=1;i<=m;++i) printf("%d\n",ans[i]); dq.clear();
	}
	return 0;
}

D. Burenka and Traditions

这里不单独讲easy version了,直接讲正解

首先不难发现我们只需要考虑长度为\(1/2\)的操作即可,因为所有更大的区间操作都可以分解,并且这样更灵活

\(pre_i=\bigoplus_\limits{1\le j\le i} a_j\),我们考虑以下一种操作方式:对于所有的\([i,i+1](1\le i<n)\)进行操作,每次异或上\(pre_i\)

不难发现经过\(i\)次操作后,\(a_j=0,j\in [1,i]\and a_{i+1}=pre_{i+1}\),最后我们对剩余的\(a_n\)单独处理即可

然后我们发现如果操作的过程中出现了某个\(pre_i=0\),那么到这个位置时会出现无需操作而\(a_{i+1}\)已经等于\(0\)的情形,相当于减少了一次操作

显然要想让答案更小,就要使得这种情况次数更多

普遍地,若\(pre_j=pre_i(j<i)\),那么\((j,i]\)必然可以节省一次操作,只需要把原来的正序操作在这个区间内逆序即可(建议手玩模拟一下)

一个naive的想法是记录\(f_i\)表示前\(i\)个数的最小操作次数,可以从所有\(pre_j=pre_i\)的位置转移过来

但是我们很容易发现最优的转移位置一定是最近的那个,因此直接用set维护所有的\(pre_j\),如果找到相同的就清空即可

#include<cstdio>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
int t,n,x,pre,ans; set <int> s;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		scanf("%d",&n); ans=pre=0; s.clear(); s.insert(0); for (RI i=1;i<=n;++i)
		{
			scanf("%d",&x); pre^=x; if (s.count(pre)) s.clear(); else ++ans; s.insert(pre);
		}
		printf("%d\n",ans);
	}
	return 0;
}

E. Fibonacci Strings

饭前随手一写就过了2333

首先不难发现\(\sum_{i=1}^k c_i\)必须是斐波那契数列的前缀和,这样我们就能确定需要分成多少项了

一眼从大到小考虑,每次找出剩下的\(c_i\)中非上次操作且最大的数,然后将其减去当前的斐波那契数

考虑证明这个做法的正确性,因为若当前的这个数不拿走\(fib_k\),那么它接下来最多拿走\(fib_{k-1}+fib_{k-3}+fib_{k-5}+\cdots\)

利用归纳法不难证明\(fib_k\ge fib_{k-1}+fib_{k-3}+fib_{k-5}+\cdots\),因此这样一定是最优的

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,k,c[N],cnt,fib[10000],lst; long long pfx[10000],sum;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (fib[0]=fib[1]=1,i=2;;++i)
	if ((fib[i]=fib[i-1]+fib[i-2])>1e9) { cnt=i-1; break; }
	for (pfx[0]=i=1;i<=cnt;++i) pfx[i]=pfx[i-1]+fib[i];
	for (scanf("%d",&t);t;--t)
	{
		for (sum=0,scanf("%d",&k),i=1;i<=k;++i) scanf("%d",&c[i]),sum+=c[i];
		int pos=-1; for (i=0;i<=cnt&&!~pos;++i) if (sum==pfx[i]) pos=i;
		if (!~pos) { puts("NO"); continue; } for (lst=0;~pos;--pos)
		{
			int mx=-1,num; for (i=1;i<=k;++i) if (i!=lst&&c[i]>mx) mx=c[i],num=i;
			if (mx<fib[pos]) break; c[num]-=fib[pos]; lst=num;
		}
		puts(~pos?"NO":"YES");
	}
	return 0;
}

F. Tonya and Burenka-179

一个重要性质没想到,直接G了

首先我们发现若\(k|n\),那么就是一些循环,否则就是所有数的和

刚开始以为要处理所有的\(n\)的约数,但这样复杂度是寄的

考虑若\(k_1|n,k_2|n,k_1|k_2\),则我们只需要考虑\(k_2\)而无需考虑\(k_1\),因为\(k_1\)相当于把\(k_2\)的集合拆分成了\(\frac{k_2}{k_1}\)份,此时必然存在某一份的平均值大于原来的平均值

因此我们只需要考虑\(n\)的所有极大约数即可,具体地,设\(n=p_1^{a_1}p_2^{a_2}p_3^{a_3}\cdots p_k^{a_k}\),我们只需要考虑\(\frac{n}{p_1},\frac{n}{p_2},\frac{n}{p_3},\cdots,\frac{n}{p_k}\)即可

由于\(n\)的质因数是\(\log\)级别的,因此极大约数也是\(\log\)级别的,初始时先暴力一遍求出答案,然后我们发现问题就是单点修改,问整体最大值

直接用两个堆来实现可删除堆即可,单组数据复杂度\(O(q\log^2 n)\)

#include<cstdio>
#include<vector>
#include<queue>
#define RI register int
#define CI const int&
#define LL long long
using namespace std;
const int N=200005;
int t,n,q,a[N],x,y,cnt,fac[N]; vector <LL> sum[N]; priority_queue <LL> add,del;
inline LL get(void)
{
	while (!add.empty()&&!del.empty()&&add.top()==del.top()) add.pop(),del.pop(); return add.top();
}
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),i=0;i<n;++i) scanf("%d",&a[i]);
		for (cnt=0,x=n,i=2;i<=x;++i) if (x%i==0)
		{
			fac[++cnt]=n/i; while (x%i==0) x/=i;
		}
		while (!add.empty()) add.pop(); while (!del.empty()) del.pop();
		for (i=1;i<=cnt;++i)
		{
			int k=fac[i]; sum[i].resize(k); for (j=0;j<k;++j) sum[i][j]=0;
			for (j=0;j<n;++j) sum[i][j%k]+=a[j];
			for (j=0;j<k;++j) add.push(sum[i][j]*k);
		}
		printf("%lld\n",get()); while (q--)
		{
			scanf("%d%d",&x,&y); --x; for (i=1;i<=cnt;++i)
			{
				int k=fac[i]; del.push(sum[i][x%k]*k);
				sum[i][x%k]+=y-a[x]; add.push(sum[i][x%k]*k);
			}
			a[x]=y; printf("%lld\n",get());
		}
	}
	return 0;
}

(Div. 1)D. Permutation for Burenka

woc尼玛的这场的Div1难度恐怖,后三题都是3300+的题,刚开始傻傻地刚这道题直接做了一个下午(只有英文题解看的脑壳疼)

首先从区间最大值我们很容易想到笛卡尔树,我们先对\(p\)建出笛卡尔树,然后考虑若\(a\)的每个元素都确定了,那么只需要验证对于树上的每一对\(u,v\),满足\(v\)\(u\)的子树内,是否均有\(a_u>a_v\),若均符合我们称其为相似树

那么有空位怎么办呢,我们可以放宽上面的条件,若\(a_u,a_v\)均不为\(0\),则需要\(a_u>a_v\),但若\(a_u,a_v\)均为\(0\)亦满足条件,我们把符合这个条件的树称为类相似树

接下来有一个结论,若\(a\)\(p\)的类相似树,则必然存在某种构造方案,使得\(a\)\(p\)的相似树

考虑证明,首先我们不妨找出任意一种构造方案,对于每个节点\(u\),设其所有子树中的最大值为\(v\),若\(a_u>a_v\)则合法无事发生

否则若\(a_u<a_v\),由类相似树的定义可知\(u,v\)的值一定都是填入的(即\(a_u,a_v\)初始时均为\(0\)),那我们直接交换\(a_u,a_v\)即可

因此现在问题转化为如何判断\(a\)是否是\(p\)的类相似树,考虑利用笛卡尔树的性质

\(l_u=\max_\limits{v\in \operatorname{subtree}(u)} a_v,r_u=\min_\limits{u\in \operatorname{subtree}(v)} a_v\),不难发现此时需要满足所有的\(l_i\le a_i\le r_i\)均成立

于是问题转化为,有\(k-1\)个已经确定的数和\(k\)段区间,每次给出剩下的一个数,问是否存在一种完美的数和区间的匹配方案

这里有一个很神的结论,最终合法的数是一个区间,具体证明我策了半天不太懂,这里直接贴一下某大佬的证法(是用霍尔定理证的)

然后问题就转化为一个经典的贪心问题了,我们把所有区间排序后双指针加入每个点,贪心地匹配即可,不过这里注意要维护最后的合法区间需要用到线段树

单组数据复杂度\(O(n\log n)\)

#include<cstdio>
#include<utility>
#include<algorithm>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=300005,INF=1e9;
int t,n,q,m,x,a[N],b[N],c[N],rst[N<<1],tot,l[N],r[N]; pi itv[N];
class Cartesian_tree
{
	private:
		int ch[N][2],stk[N],top,rt;
		#define lc(x) ch[x][0]
		#define rc(x) ch[x][1]
		inline void DFS(CI now,CI fa)
		{
			r[now]=r[fa]; if (b[now]) r[now]=min(r[now],b[now]); l[now]=b[now];
			for (RI i=0;i<=1;++i) if (ch[now][i]) DFS(ch[now][i],now),l[now]=max(l[now],l[ch[now][i]]);
		}
	public:
		inline void build(void)
		{
			RI i; for (top=stk[1]=0,i=1;i<=n;++i)
			{
				lc(i)=rc(i)=0; while (top&&a[i]>a[stk[top]]) --top;
				if (top) lc(i)=rc(stk[top]),rc(stk[top])=i; else lc(i)=stk[1]; stk[++top]=i;
			}
			rt=stk[1]; r[0]=INF; DFS(rt,0);
		}
		#undef lc
		#undef rc
}CT;
class Segment_Tree
{
	private:
		struct segment
		{
			int tag; pi mi;
		}node[N<<3];
		#define T(x) node[x].tag
		#define M(x) node[x].mi
		#define TN CI now=1,CI l=1,CI r=tot
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void pushup(CI now)
		{
			M(now)=min(M(now<<1),M(now<<1|1));
		}
		inline void apply(CI now,CI v)
		{
			T(now)+=v; M(now).fi+=v;
		}
		inline void pushdown(CI now)
		{
			if (T(now)) apply(now<<1,T(now)),apply(now<<1|1,T(now)),T(now)=0;
		}
	public:
		inline void build(TN)
		{
			T(now)=0; if (l==r) return (void)(M(now)=mp(0,l));
			int mid=l+r>>1;	build(LS); build(RS); pushup(now);
		}
		inline void modify(CI beg,CI end,CI v,TN)
		{
			if (beg<=l&&r<=end) return apply(now,v); int mid=l+r>>1; pushdown(now);
			if (beg<=mid) modify(beg,end,v,LS); if (end>mid) modify(beg,end,v,RS); pushup(now);
		}
		inline pi query(void)
		{
			return M(1);
		}
		#undef T
		#undef M
		#undef TN
		#undef LS
		#undef RS
}ST;
inline int get(CI x)
{
	return lower_bound(rst+1,rst+tot+1,x)-rst;
}
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,&q),i=1;i<=n;++i) scanf("%d",&a[i]);
		for (m=0,i=1;i<=n;++i) scanf("%d",&b[i]),m+=!b[i];
		for (i=1;i<m;++i) scanf("%d",&c[i]); sort(c+1,c+m);
		for (tot=0,i=1;i<=n;++i) rst[++tot]=a[i],rst[++tot]=b[i]; rst[++tot]=INF;
		sort(rst+1,rst+tot+1); tot=unique(rst+1,rst+tot+1)-rst-1;
		bool flag=1; for (CT.build(),i=1;i<=n&&flag;++i) if (l[i]>r[i]) flag=0;
		for (m=0,i=1;i<=n;++i) if (!b[i]) itv[++m]=mp(l[i],r[i]);
		sort(itv+1,itv+m+1); ST.build(); int L=0,R=INF,now=m-1;
		for (i=m;i&&flag;--i)
		{
			ST.modify(get(itv[i].se),tot,-1);
			while (now&&c[now]>=itv[i].fi) ST.modify(get(c[now--]),tot,1);
			pi rt=ST.query(); if (rt.fi<-1) flag=0; else
			if (rt.fi==-1) L=max(L,itv[i].fi),R=min(R,rst[rt.se]);
		}
		while (q--) scanf("%d",&x),puts(flag&&x>=L&&x<=R?"YES":"NO");
	}
	return 0;
}

Postscript

明天就要开始上网课了,不知道还有没有时间打代码了……

posted @ 2022-09-02 15:54  空気力学の詩  阅读(101)  评论(0)    收藏  举报