Codeforces Round #837 (Div. 2)

Preface

补题ing

上周由于疫情鸽了好多场,趁现在空下来尽量多写点吧


A. Hossam and Combinatorics

SB题,直接统计下最大的数和最小的数的个数即可

注意所有数相同的情况要特判下

#include<cstdio>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
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,j; for (scanf("%d",&n),i=1;i<=n;++i)	scanf("%d",&a[i]);
		for (sort(a+1,a+n+1),i=2;i<=n&&a[i]==a[1];++i);
		if (i>n) { printf("%lld\n",1LL*n*(n-1)); continue; }
		for (j=n-1;j>=1&&a[j]==a[n];--j);
		printf("%lld\n",2LL*(i-1)*(n-j));
	}
	return 0;
}

B. Hossam and Friends

考虑每一个隔断区间\([l,r]\),若右端点\(R\ge r\),则合法的左端点\(L\)不能\(\le l\)

那么我们可以开一个vector,记录下每个隔断区间的右端点对应的左端点

然后依次枚举右端点的位置,左端点的最小取值就是经过的所有vector里元素的最大值再加一

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

#include<cstdio>
#include<vector>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,m,l,r; vector <int> pos[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,&m),i=1;i<=n;++i) pos[i].clear();
		for (i=1;i<=m;++i) scanf("%d%d",&l,&r),pos[max(l,r)].push_back(min(l,r));
		long long ans=0; int lst=0; for (i=1;i<=n;++i)
		{
			for (int x:pos[i]) lst=max(lst,x); ans+=i-lst;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

C. Hossam and Trainees

SB题,直接分解质因数判断即可

但是要注意\(O(n\sqrt {a_i})\)是会TLE的,因此我们可以先预处理筛出\(\sqrt {a_i}\)范围内的质数,这样复杂度降到\(O(n\times \pi(\sqrt{a_i}))\)即可通过

#include<cstdio>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N],prm[N],cnt; bool vis[N]; set <int> s;
inline void init(CI n)
{
	for (RI i=2,j;i<=n;++i)
	{
		if (!vis[i]) prm[++cnt]=i;
		for (j=1;j<=cnt&&i*prm[j]<=n;++j)
		if (vis[i*prm[j]]=1,i%prm[j]==0) break;
	}
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (init(100000),scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
		bool flag=0; for (s.clear(),i=1;i<=n&&!flag;++i)
		{
			for (j=1;j<=cnt&&1LL*prm[j]*prm[j]<=a[i];++j)
			if (a[i]%prm[j]==0)
			{
				if (s.count(prm[j])) flag=1; s.insert(prm[j]);
				while (a[i]%prm[j]==0) a[i]/=prm[j];
			}
			if (a[i]>1)
			{
				if (s.count(a[i])) flag=1; s.insert(a[i]);
			}
		}
		puts(flag?"YES":"NO");
	}
	return 0;
}

D. Hossam and (sub-)palindromic tree

之前好像写过类似的思路的题(还是OI时期了),但是时间太长忘记了

思想就是序列上的区间DP上树的常见套路,最长回文子序列的序列版本很好处理,我们设\(f_{l,r}\)表示区间\([l,r]\)的答案,则:

  • 先考虑两个端点跳过的情况,\(f_{l,r}=\max(f_{l+1,r},f_{l,r-1})\)

  • \(s_l=s_r\),有转移\(f_{l,r}=\max(f_{l,r},f_{l+1,r-1}+2)\)

然后我们发现这个东西拿到树上依然是可以转移的,我们设\(f_{x,y}\)表示树上两点\(x,y\)之间的答案

不难发现如果\(x,y\)没有祖先关系,上面的\(l+1,r-1\)对应的就是\(x,y\)的父节点

如果有祖先关系(设\(x\)\(y\)的祖先),那么区别就是我们此时要找到\(x\)\(y\)方向的儿子来转移

这部分可以在树上倍增结合LCA实现,由于这种区间DP在序列上的复杂度是\(O(n^2)\)

我们把它搬到树上也就多了每次转移一个\(\log\)的复杂度,因此总复杂度\(O(n^2\log n )\)

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=2005,P=12;
int t,n,x,y,ans,dep[N],anc[N][P],f[N][N]; char s[N]; vector <int> v[N];
inline void DFS(CI now=1,CI fa=0)
{
	dep[now]=dep[fa]+1; anc[now][0]=fa;
	for (RI i=0;i<P-1;++i) anc[now][i+1]=anc[anc[now][i]][i];
	for (int to:v[now]) if (to!=fa) DFS(to,now);
}
inline int getLCA(int x,int y)
{
	if (dep[x]<dep[y]) swap(x,y);
	for (RI i=P-1;~i;--i) if (dep[anc[x][i]]>=dep[y]) x=anc[x][i];
	if (x==y) return x;
	for (RI i=P-1;~i;--i) if (anc[x][i]!=anc[y][i])
	x=anc[x][i],y=anc[y][i]; return anc[x][0];
}
inline int jump(int x,CI y)
{
	for (RI i=0;i<P;++i) if ((y>>i)&1) x=anc[x][i]; return x;
}
inline int DP(CI x,CI y)
{
	if (!x||!y) return 0;
	if (~f[x][y]) return f[x][y]; if (x==y) return f[x][y]=1;
	int fa=getLCA(x,y),fx=anc[x][0],fy=anc[y][0];
	if (fa==x) fx=jump(y,dep[y]-dep[x]-1);
	else if (fa==y) fy=jump(x,dep[x]-dep[y]-1);
	f[x][y]=max(DP(fx,y),DP(fy,x));
	if (s[x]!=s[y]) return f[x][y];
	if (fx==y&&fy==x) f[x][y]=max(f[x][y],2);
	else f[x][y]=max(f[x][y],DP(fx,fy)+2); return f[x][y];
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d%s",&n,s+1),i=1;i<=n;++i)
		for (v[i].clear(),j=1;j<=n;++j) f[i][j]=-1;
		for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
		for (i=1;i<=n;++i) for (j=0;j<P;++j) anc[i][j]=0;
		for (ans=0,DFS(),i=1;i<=n;++i) for (j=1;j<=n;++j)
		ans=max(ans,DP(i,j)); printf("%d\n",ans);
	}
	return 0;
}

E. Hossam and a Letter

纯暴力枚举题,但是有些细节容易写挂(不过写的时候感觉比较好,编译过了就直接过了)

考虑形成一个H的关键,其实就是要确定转折处的那两个位置的坐标

换句话说,我们可以枚举\(x,y,z\),表示H的横行在第\(x\)行,两个纵列分别在\(y,z\)

这样我们只需要知道两个转折点分别最多向上和向下延申多少即可

不难想到预处理一下,\(up_{0/1,i,j}\)表示从\((i,j)\)往上(包括自身),经过\(0/1\)m最多可以延申几个格子

同理\(down_{0/1,i,j}\)表示从\((i,j)\)往下(不包括自身),经过\(0/1\)m最多可以延申几个格子

然后根据中间的横行用了多少个m分类讨论下就完了,细节可以看代码

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

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=405;
int n,m,up[2][N][N],down[2][N][N],ans; char s[N][N];
inline void check(CI A,CI B,CI C,CI D,CI x,CI y,CI z)
{
	int Up=min(up[A][x][y],up[B][x][z]),Down=min(down[C][x][y],down[D][x][z]);
	if (Up>1&&Down>=1) ans=max(ans,2*(Up+Down)+z-y-1);
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j,k; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%s",s[i]+1);
	for (i=1;i<=n;++i) for (j=1;j<=m;++j)
	{
		if (s[i][j]=='#') continue; int ct=0;
		for (k=i;k>=0;--k)
		{
			if (k==0)
			{
				if (ct==1) up[ct][i][j]=i;
				else up[0][i][j]=up[1][i][j]=i; break;
			}
			if (s[k][j]=='#') { up[ct][i][j]=i-k; break; }
			else if (s[k][j]=='m')
			{
				up[ct][i][j]=i-k; ++ct;
				if (ct>1) break;
			}
		}
		if (!ct) up[1][i][j]=up[0][i][j];
		for (ct=0,k=i+1;k<=n+1;++k)
		{
			if (k==n+1)
			{
				if (ct==1) down[ct][i][j]=n-i;
				else down[0][i][j]=down[1][i][j]=n-i; break;
			}
			if (s[k][j]=='#') { down[ct][i][j]=k-i-1; break; }
			else if (s[k][j]=='m')
			{
				down[ct][i][j]=k-i-1; ++ct;
				if (ct>1) break;
			}
		}
		if (!ct) down[1][i][j]=down[0][i][j];
	}
	for (j=1;j<m-1;++j) for (i=2;i<=n-1;++i)
	{
		if (s[i][j]=='#') continue;
		int ct=0; for (k=j+1;k<m;++k)
		{
			if (s[i][k]=='#') break;
			if (s[i][k]=='m') ++ct; if (ct>1) break;
			if (ct==1) check(0,0,0,0,i,j,k+1);
			else check(1,0,0,0,i,j,k+1),check(0,1,0,0,i,j,k+1),
			check(0,0,1,0,i,j,k+1),check(0,0,0,1,i,j,k+1);
		}
	}
	return printf("%d",ans),0;
}

F. Hossam and Range Minimum Query

没想到可以通过把数随机映射来避免被构造数据卡,学到了学到了

首先如果离线的话就是个莫队裸题,但强制在线的话一般区间询问就要考虑主席树

但是主席树里应该维护什么信息呢,总不可能要记下每个数出现的个数吧

考虑题目中询问的出现次数是奇数的限制,我们不难把它和异或联系起来

我们考虑维护一颗主席树,节点存储区间的异或和,不难发现由于异或有可减性因此是可行的

那么我们只要判断某个离散化后的值域区间的值是否为\(0\)即可

但想到这里容易发现对于形如1 2 3这样的数据处理到就GG了,遂去看Tutorial

随即发现由于我们在维护的时候不关心数具体是什么,只关心它是否重复

因此可以给每个数随机赋一个unsigned long long范围的值,这样撞车的概率就很小了

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

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<ctime>
#define RI register int
#define CI const int&
using namespace std;
typedef unsigned long long u64;
const int N=200005;
int n,q,a[N],rst[N],cnt,l,r,ans,rt[N]; u64 rnd[N];
class Chairman_Tree
{
	private:
		struct segment
		{
			u64 val; int ch[2];
		}node[N*30]; int tot;
	public:
		#define V(now) node[now].val
		#define ls(now) node[now].ch[0]
		#define rs(now) node[now].ch[1]
		#define TN CI l=1,CI r=cnt
		inline void insert(int& now,CI lst,CI pos,const u64& val,TN)
		{
			now=++tot; node[now]=node[lst]; V(now)^=val; if (l==r) return;
			int mid=l+r>>1; if (pos<=mid) insert(ls(now),ls(lst),pos,val,l,mid);
			else insert(rs(now),rs(lst),pos,val,mid+1,r);
		}
		inline int query(CI x,CI y,TN)
		{
			if (V(x)==V(y)) return 0; if (l==r) return l; int mid=l+r>>1;
			if (V(ls(x))!=V(ls(y))) return query(ls(x),ls(y),l,mid);
			else return query(rs(x),rs(y),mid+1,r);
		}
		#undef V
		#undef ls
		#undef rs
		#undef TN
}T;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]),rst[i]=a[i];
	sort(rst+1,rst+n+1); cnt=unique(rst+1,rst+n+1)-rst-1; srand(time(0));
	for (i=1;i<=cnt;++i) rnd[i]=1ull*rand()*rand()*rand()*rand()*rand();
	for (i=1;i<=n;++i) a[i]=lower_bound(rst+1,rst+cnt+1,a[i])-rst,T.insert(rt[i],rt[i-1],a[i],rnd[a[i]]);
	for (scanf("%d",&q),i=1;i<=q;++i)
	{
		scanf("%d%d",&l,&r); l^=ans; r^=ans;
		printf("%d\n",ans=rst[T.query(rt[l-1],rt[r])]);
	}
	return 0;
}

Postscript

闪总啊闪总,你不能再沉迷推Gal了!速度写题!!!

posted @ 2022-12-22 20:57  空気力学の詩  阅读(69)  评论(2编辑  收藏  举报