AtCoder Regular Contest 149

Preface

最近国庆在外面玩的有点high啊,欠了一篇AT和两篇CF没写,今天先浅浅写一下这场

当时10.2号在外面玩得有点晚了,到寝室刚好比赛开始,晚饭都没吃就开干了

主要是C写的太久了,而且挂了一发之后看了好久才发现\(n=3\)的情形没处理好

然后40min做D没做出来,鉴定为纯纯的FW(话说原来ARC这么难的吗,感觉比以前的一些AGC还难)


A - Repdigit Number

送分题,暴力枚举每一种情况判断即可

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
int n,m,md,mn=-1;
inline void print(CI d,CI n)
{
	for (RI i=1;i<=n;++i) putchar(d+'0');
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (scanf("%d%d",&n,&m),j=1;j<=9;++j)
	{
		int cur=0; for (i=1;i<=n;++i)
		if ((cur=(10LL*cur+j)%m)==0)
		{
			if (i>mn||(i==mn&&j>md)) mn=i,md=j;
		}
	}
	if (~mn) print(md,mn); else puts("-1");
	return 0;
}

B - Two LIS Sum

一眼猜结论题,不难发现我们将\(A\)排成\(1,2,\cdots,n\)时答案一定不会变劣,因此在此基础上求出\(B\)的LIS即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005;
int n,a[N],x,b[N],cur,ans;
class Tree_Array
{
	private:
		int bit[N];
	public:
		#define lowbit(x) (x&-x)
		inline void add(int x,CI y)
		{
			for (;x<=n;x+=lowbit(x)) bit[x]=max(bit[x],y);
		}
		inline int get(int x,int ret=0)
		{
			for (;x;x-=lowbit(x)) ret=max(ret,bit[x]); return ret;
		}
		#undef lowbit
}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]);
	for (i=1;i<=n;++i) scanf("%d",&x),b[a[i]]=x;
	for (i=1;i<=n;++i) cur=T.get(b[i]),T.add(b[i],cur+1),ans=max(ans,cur+1);
	return printf("%d",ans+n),0;
}

C - Avoid Prime Sum

又是比较显然的构造题,不难发现奇数+奇数、偶数+偶数的和一定为合数,因此我们考虑尽可能地让奇数之间,偶数之间尽量邻近在一起

因此就有一个naive的想法,根据\(n\)的奇偶性把棋盘分成两个部分(如下图),每个部分各自填奇数和偶数,然后只要考虑交界处的情况了

偶数的情况比较容易,只要找到一个奇数且不是质数的数然后拆分一下即可,但奇数时要讨论最中间的奇数要和两个偶数之和为合数

可能我的实现比较渣,代码量挺大,而且还需要特判\(n=3\)的情况(话说我\(n=3\)的情况手玩玩不出来最后还是暴枚出来的)

后来看了眼官方sol发现可以直接用\(3\)的倍数来构造,这样大大降低了代码复杂度

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
const int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
int n,a[N][N],tar,odd[N],even[N],spo=-1,spe,arr[N]; bool vis[N*N];
inline bool is_prime(CI n)
{
	for (RI i=2;i*i<=n;++i)
	if (n%i==0) return 0; return 1;
}
inline bool check(CI t)
{
	for (RI i=1;i<=n;++i)
	{
		odd[i]=i; even[i]=t-i; if ((t-i)&1) swap(odd[i],even[i]);
		if (i!=1&&!is_prime(even[i]+1)) spo=odd[i],spe=even[i];
	}
	return ~spo;
}
inline void sp_solve(void)
{
	RI i,j,k; for (i=1;i<=9;++i) arr[i]=i;
	do
	{
		for (k=0,i=1;i<=3;++i) for (j=1;j<=3;++j) a[i][j]=arr[++k];
		bool flag=1; for (i=1;i<=3&&flag;++i) for (j=1;j<=3&&flag;++j) for (k=0;k<4&&flag;++k)
		{
			int ii=i+dx[k],jj=j+dy[k]; if (ii<1||ii>3||jj<1||jj>3) continue;
			if (is_prime(a[i][j]+a[ii][jj])) flag=0;
		}
		if (flag) return;
	} while (next_permutation(arr+1,arr+10));
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j,k; if (scanf("%d",&n),n==3) sp_solve(); else
	{
		if (n&1)
		{
			for (i=2*n+1;i<=n*n;i+=2)
			if (!is_prime(i)&&check(i)) break;
			a[(n+1)/2][(n+1)/2]=1; a[(n+1)/2+1][(n+1)/2]=even[1];
			a[(n+1)/2][(n+1)/2+1]=spe; a[(n+1)/2-1][(n+1)/2+1]=spo;
			vis[1]=vis[even[1]]=vis[spe]=vis[spo]=1;
			for (i=j=1;i<(n+1)/2;++i)
			{
				if (odd[++j]==spo) ++j; vis[a[(n+1)/2][i]=odd[j]]=1;
			}
			for (i=(n+1)/2+2;i<=n;++i)
			{
				if (odd[++j]==spo) ++j; vis[a[(n+1)/2-1][i]=odd[j]]=1;
			}
			for (i=j=1;i<(n+1)/2;++i)
			{
				if (even[++j]==spe) ++j; vis[a[(n+1)/2+1][i]=even[j]]=1;
			}
			for (i=(n+1)/2+2;i<=n;++i)
			{
				if (even[++j]==spe) ++j; vis[a[(n+1)/2][i]=even[j]]=1;
			}
			for (k=1,i=1;i<(n+1)/2-1;++i) for (j=1;j<=n;++j)
			{
				while (vis[k]) k+=2; vis[a[i][j]=k]=1;
			}
			for (i=1;i<=(n+1)/2;++i)
			{
				while (vis[k]) k+=2; vis[a[(n+1)/2-1][i]=k]=1;
			}
			for (k=2,i=(n+1)/2+2;i<=n;++i) for (j=1;j<=n;++j)
			{
				while (vis[k]) k+=2; vis[a[i][j]=k]=1;
			}
			for (i=(n+1)/2+1;i<=n;++i)
			{
				while (vis[k]) k+=2; vis[a[(n+1)/2+1][i]=k]=1;
			}
		} else
		{
			for (i=2*n+1;i<=n*n;i+=2)
			if (!is_prime(i)) { check(i); break; }
			for (i=1;i<=n;++i) vis[a[n/2][i]=odd[i]]=1,vis[a[n/2+1][i]=even[i]]=1;
			for (k=1,i=1;i<n/2;++i) for (j=1;j<=n;++j)
			{
				while (vis[k]) k+=2; vis[a[i][j]=k]=1;
			}
			for (k=2,i=n/2+2;i<=n;++i) for (j=1;j<=n;++j)
			{
				while (vis[k]) k+=2; vis[a[i][j]=k]=1;
			}
		}
	}
	for (i=1;i<=n;++i) for (j=1;j<=n;++j) printf("%d%c",a[i][j]," \n"[j==n]);
	return 0;
}

D - Simultaneous Sugoroku

当时比赛的时候想的方向有点偏了,结果G了

首先我们直接考虑所有的\([1,10^6]\)的pieces,然后可以发现一个重要的性质:

对于\(t,-t\)两个位置的pieces,它们如果到达原点那时间必然时相同的,若无法到达最后的位置关于原点对称

初始时所有的pieces都在一个半轴上,不难发现在每次移动后就可能会出现正负半轴上都有pieces的情况

但我们利用上面的性质,总是可以把其中那个pieces数量较少的半轴上的pieces全部合并到另一个半轴上去,具体地我们可以连一条边在这两个点之间(可以看下图帮助理解)

然后每次有piece到达原点的时候统计答案即可,最后所有点之间构成了一个森林,更新答案即可

#include<cstdio>
#include<vector>
#define RI register int
#define CI const int&
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int N=1000005;
int n,m,x[N],d[N]; pair <int,int> ans[N]; vector <int> v[N]; bool vis[N];
inline void DFS(CI now)
{
	vis[now]=1; for (int to:v[now]) if (!vis[to])
	{
		if (ans[now].fi==1) ans[to]=ans[now]; else ans[to]=mp(0,-ans[now].se); DFS(to);
	}
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",&x[i]);
	for (i=1;i<=m;++i) scanf("%d",&d[i]);
	for (i=1;i<=1000000;++i) ans[i].first=-1; int l=1,r=1000000,mid,sum=0;
	for (i=1;i<=m;++i)
	{
		if (l>r) break; if (l+sum>0) sum-=d[i]; else sum+=d[i]; mid=-sum;
		if (mid<l||mid>r) continue; ans[mid]=mp(1,i);
		if (mid-l<=r-mid)
		{
			for (j=l;j<mid;++j) v[2*mid-j].pb(j); l=mid+1;
		} else
		{
			for (j=mid+1;j<=r;++j) v[2*mid-j].pb(j); r=mid-1;
		}
	}
	for (i=l;i<=r;++i) ans[i]=mp(0,i+sum);
	for (i=1;i<=1000000;++i) if (!vis[i]&&~ans[i].fi) DFS(i);
	for (i=1;i<=n;++i) printf("%s %d\n",ans[x[i]].fi?"Yes":"No",ans[x[i]].se);
	return 0;
}

E - Sliding Window Sort

好棒的思维题,感觉好久没做到过这种像做AGC一样沁人心脾的题目了(话说现在的ARC就是以前AGC了吧)

首先考虑把问题转化一下,原来的操作是先给一个区间排序,然后再把窗口右移一格,现在我们把窗口固定下来,然后把数列左移一格,比如样例1:

\((4,1,5,6,2,3)\to(4,5,6,2,3,1)\to(5,6,2,3,1,4)\to (5,6,3,1,4,2)\to (5,6,1,4,2,3)\to(5,6,4,2,3,1)\)

现在我们考虑简化问题,把数列分成左边的\(L=M-1\)个元素和右边的\(R=N-(M-1)\)个元素

当执行了一次或更多次操作后,左边的\(M-1\)个元素是单调上升的,因此我们可以把这部分元素用一个集合来看待

现在问题转化为这样,有一个\(L\)个元素的集合和一个有\(R\)个元素的序列,每次操作我们可以把序列中最左侧的元素移到集合里,然后把集合里最小的元素移动到序列的最右侧

比如对于前面的样例有:

\(\{1,4\}(5,6,2,3)\to\{4,5\}(6,2,3,1)\to \{5,6\}(2,3,1,4)\to \{5,6\}(3,1,4,2)\to \{5,6\}(1,4,2,3)\to \{5,6\}(4,2,3,1)\)

接下来由于\(K\)的范围很大,因此我们考虑缩小它的范围,不难发现当\(K=R\)时集合中的元素就是\(\{R+1,R+2,\cdots,N\}\),此时序列中的元素对集合不会造成任何影响,因此只需要考虑序列的情况

不难发现我们把多余的操作次数移动直接计算掉即可,而\(K<R\)是更加简单了,序列中有一部分元素不可能被用到,直接把它们删掉即可

那么现在问题变成对于集合\(\{R+1,R+2,\cdots,N\}\)(若不为这个答案显然就是\(0\))和序列\((x_1,x_2,\cdots,x_R)\),我们需要统计出初始的状态\(\{a_1,a_2,\cdots,a_L\}(b_1,b_2,\cdots,b_R)\)满足以下条件:

  • \(a_1,a_2,\cdots,a_L,b_1\)中存在某个数等于\(x_1\)
  • \(a_1,a_2,\cdots,a_L,b_1,b_2\)中存在某个数等于\(x_2\)
  • \(a_1,a_2,\cdots,a_L,b_1,b_2,b_3\)中存在某个数等于\(x_3\)
  • \(\cdots\cdots\)

但是我们考虑到若\(x_{i-1}>x_i\),则\(b_i\)的值只能是\(x_i\),因为\(b_i\)前面的所有数都一定大于等于\(x_{i-1}\),因此只能让它来等于\(x_i\)

这样我们假设剔除了这些确定的\(b_i\)后序列中待定的数有\(R'\)个,由于以上的每一个局面都是在\(L+1\)个数中选一个等于\(x_i\),因此总方案数为\((L+1)^{R'}\)

最后由于第一次排序之前集合中的元素可以随意排列,因此答案要再乘上\(L!\)

真是一道妙题!

#include<cstdio>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005,mod=998244353;
int n,m,k,L,R;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; scanf("%d%d%d",&n,&m,&k); vector <int> b(n);
	for (i=0;i<n;++i) scanf("%d",&b[i]);
	rotate(b.begin(),b.begin()+(k%n),b.end());
	vector <int> left={b.begin(),b.begin()+m-1},right={b.begin()+m-1,b.end()};
	L=left.size(); R=right.size();
	if (k<R) right={right.end()-k,right.end()},R=k;
	else if (k>R) rotate(right.begin(),right.end()-(k-R)%R,right.end()),k=R;
	bool flag=1; for (i=0;i<left.size()-1&&flag;++i) if (left[i]>left[i+1]) flag=0;
	for (i=0;i<right.size()&&flag;++i) if (right[i]>left[0]) flag=0;
	if (!flag) return puts("0"),0;
	int pre=0,ans=1; for (int cur:right)
	{
		if (pre>cur) continue; pre=cur; ans=1LL*ans*(L+1)%mod;
	}
	for (i=1;i<=L;++i) ans=1LL*ans*i%mod;
	return printf("%d",ans),0;
}

Postscript

最后把这场结了吧,最近开的坑好多但都没时间补,主要是线下上课了时间变得少了很多

F的难度对于现在的我来说还是太吃力了点,先跑路了

posted @ 2022-10-04 23:24  空気力学の詩  阅读(96)  评论(0编辑  收藏  举报