Codeforces Round 875 (Div. 2)

Preface

难得现场打一次,这天下午打了三个半小时的校内赛,然后八点打了两小时ARC,最后再接上两个半小时的CF真是爽歪歪

不过有一说一其实很多时候都在坐牢,这场CF也差不多,一个小时写完ABCD然后开始坐牢,其实E基本想出来了但是没想到用随机赋值的方法来实现

不过由于这场手很稳因此排名极高(Div2 Rank22),因此上了波大分(小号分数要直逼大号了)


A. Twin Permutations

直接令\(b_i=n+1-a_i\),这样最后所有的\(a_i+b_i\)都为\(n+1\),满足题意

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

B. Array merging

屌题面写的有点难懂,猜了几分钟才猜出题意

开两个桶分别统计下两个串中每种数的最长连续段长度,然后贡献就直接相加即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=400005;
int t,n,a[N],b[N],ca[N],cb[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<=2*n;++i) ca[i]=cb[i]=0;
		for (i=1;i<=n;++i) scanf("%d",&a[i]);
		for (i=1;i<=n;++i) scanf("%d",&b[i]);
		int lst=0; for (a[n+1]=0,i=1;i<=n;++i)
		if (a[i]!=a[i+1]) ca[a[i]]=max(ca[a[i]],i-lst),lst=i;
		lst=0; for (b[n+1]=0,i=1;i<=n;++i)
		if (b[i]!=b[i+1]) cb[b[i]]=max(cb[b[i]],i-lst),lst=i;
		int ret=0; for (i=1;i<=2*n;++i) ret=max(ret,ca[i]+cb[i]);
		printf("%d\n",ret);
	}
	return 0;
}

C. Copil Copac Draws Trees

\(f_i\)表示给\(i\)染色需要的最少轮数,然后按照题面所给的顺序给每条边一个权值

\(u\)的父边权值为\(lst\),对于\(u\to v\)这条边,显然有转移\(f_v=f_u+[w<lst]\),直接DFS一遍即可

#include<cstdio>
#include<iostream>
#include<vector>
#include<utility>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
int t,n,x,y,f[N]; vector <pi> v[N];
inline void DFS(CI now=1,CI fa=0,CI lst=0)
{
	for (auto [to,w]:v[now]) if (to!=fa) f[to]=f[now]+(w<lst),DFS(to,now,w);
}
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) v[i].clear();
		for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(pi(y,i)),v[y].push_back(pi(x,i));
		int ret=0; for (f[1]=1,DFS(),i=1;i<=n;++i) ret=max(ret,f[i]);
		printf("%d\n",ret);
	}
	return 0;
}

D. The BOSS Can Count Pairs

这场说实话运气挺好的,D是比较擅长的根号分治的题所以一看到就会了,然后交之前也调对了各种参数一发入魂(其实E也挺合胃口的,就是做了那么多随机赋权的题这次没想到,可能是因为对排名没啥影响所以摆了)

回到这题由于\(a_i,b_i\le n\),因此等式的右边的和式最大值也就\(2n\),而左边又是个乘法,因此很容易想到根号分治

设阈值\(S\),当\(a_i>S\)时,很显然\(a_j\)的取值只有\(\frac{2n}{S}\)种,我们可以从后往前扫一遍,对于每种\(a_j\),用一个桶来存它们对应的\(b_j\)出现的次数

刚开始这里写unordered_map的,不过后面一想时限很紧所以就先没交,然后发现可能产生贡献的\(a_j\le \frac{2n}{S}\),因此可以暴力开数组来存(但是要注意别爆内存了)

\(a_i\le S\)时,我们可以直接枚举一个\(t\in[1,S]\),然后从左往右扫,遇到\(a_i=t\)就用桶记录下\(b_i\)出现的次数,然后再对每个数都进行统计即可

重点注意以上的两次操作的枚举顺序不能错,否则会出现多算或者少算的问题

复杂度的话,理论上取\(S=\sqrt{2n}\)是最优的,但这样数组会开不下,因此我选的是\(S=1000\),然后这样\(400\times 200000\)int数组刚好能开下,最后跑了\(1856ms\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,S=1000;
int t,n,a[N],b[N],c[N],f[405][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",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
		for (i=1;i<=n;++i) scanf("%d",&b[i]); long long ret=0;
		for (i=1;i<=S;++i)
		{
			for (j=1;j<=n;++j) c[j]=0;
			for (j=1;j<=n;++j)
			{
				if (1LL*i*a[j]-b[j]>=1&&1LL*i*a[j]-b[j]<=n) ret+=c[i*a[j]-b[j]];
				if (a[j]==i) ++c[b[j]];
			}
		}
		for (i=n;i>=1;--i)
		{
			if (a[i]>S) for (j=1;a[i]*j<=2*n;++j)
			if (a[i]*j-b[i]>=1&&a[i]*j-b[i]<=n) ret+=f[j][a[i]*j-b[i]];
			if (a[i]<=400) ++f[a[i]][b[i]];
		}
		for (i=1;i<=n;++i) if (a[i]<=400) --f[a[i]][b[i]];
		printf("%lld\n",ret);
	}
	return 0;
}

E. Hyperregular Bracket Strings

这场主要赛时后面两题过的人太少了(到结束E题9人,F题Div2都没人过),所以默认自己写不出来就弃了,其实今天一看不难的说

首先很容易发现当所有区间都不交的时候答案就是独立的,每一部分用卡特兰数求一下方案数然后乘起来就好

然后思考区间有交的情况,不难发现两个区间的交一定也必须是合法的括号序列,而且这部分的贡献计算就会和原来的独立开来

为什么区间的交也必须是合法的呢,其实可以用括号序列的经典模型,即把左括号看成\(1\),右括号看成\(-1\)

然后合法括号序列满足前缀和全部\(\ge 0\),后缀和全部\(\le 0\),然后当两个合法括号序列区间相交时,中间那一段就一定是也满足上面的限制,因此它也必须是合法的

因此这题的思路就很清楚了,我们设最后每个位置\(i\),覆盖它的区间标号集合为\(S_i\),则所有\(S\)相同的位置都要被一起考虑

然后我当时以为是线段树分治维护区间覆盖,然后判重复就歇逼了,难不成要mapvector

后来一看捏麻麻的这个套路之前做过不知道多少次了,直接给每个区间随机一个权值,然后覆盖就直接给每个位置异或上这个值即可

最后根据每个点的权值来分类即可,实现的话直接用差分,非常好写

#include<cstdio>
#include<iostream>
#include<random>
#include<map>
#define RI register int
#define CI const int&
using namespace std;
typedef unsigned long long ull;
const int N=300005,mod=998244353;
int t,n,m,fact[N],ifact[N],l,r; ull pfx[N]; map <ull,int> rst;
mt19937_64 rnd(random_device{}());
uniform_int_distribution<ull> dist(0,ULLONG_MAX);
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 void init(CI n)
{
	RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
	for (ifact[n]=quick_pow(fact[n]),i=n-1;~i;--i) ifact[i]=1LL*ifact[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
	return 1LL*fact[n]*ifact[m]%mod*ifact[n-m]%mod;
}
inline int Catalan(CI x)
{
	if (x&1) return 0; return (C(x,x/2)-C(x,x/2-1)+mod)%mod;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (init(300000),scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) pfx[i]=0;
		for (i=1;i<=m;++i)
		{
			scanf("%d%d",&l,&r); ull x=dist(rnd);
			pfx[l]^=x; pfx[r+1]^=x;
		}
		for (rst.clear(),i=1;i<=n;++i) ++rst[pfx[i]^=pfx[i-1]];
		int ret=1; for (auto [_,x]:rst) ret=1LL*ret*Catalan(x)%mod;
		printf("%d\n",ret);
	}
	return 0;
}

Postscript

妈的要考离散了,一堆定理和专有名词的英文名一点没背,准备吃席

posted @ 2023-05-29 20:31  空気力学の詩  阅读(98)  评论(0编辑  收藏  举报