CodeTON Round 2

Preface

现在菜的真实啊……比赛的时候打的慢就算了还天天写假算法,可怜……

ABCD用了1h,然后E以为不会爆long long(主要我的做法很奇怪很脑残),当场去世


A. Two 0-1 Sequences

签到题,首先我们发现一个先决条件:a的后\(m-1\)位必须和b的后\(m-1\)位相等,因为修改影响不到后面

我们发现操作的本质就是在a的前\(n-m+1\)位中任选一个保留下来,因此只需要看这些数中有没有和b的首位相同的即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=55;
int t,n,m; char a[N],b[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,pos=0; scanf("%d%d%s%s",&n,&m,a+1,b+1);
		for (i=1;i<=m;++i) if (a[n-i+1]!=b[m-i+1]) { pos=i; break; }
		if (!pos) { puts("YES"); continue; }
		if (pos!=m) { puts("NO"); continue; }
		bool flag=0; for (i=1;i<=n-m+1;++i)
		if (a[i]==b[1]) { flag=1; break; }
		puts(flag?"YES":"NO");
	}
	return 0;
}

B. Luke is a Foodie

不难发现对于一个区间\([l,r]\),它们可以被同一个数一起拿走的充要条件是\(\max_{l\le i\le r} a_i-\min_{l\le i\le r} a_i\le 2x\)

由于我们必须按顺序取走每一个数,因此直接从左往右贪心,每次取走能取的最大的区间即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int INF=1e9;
int t,n,m,x,mi,mx,ans;
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),mi=INF,ans=mx=0,i=1;i<=n;++i)
		{
			scanf("%d",&x); mi=min(mi,x); mx=max(mx,x);
			if (mx-mi>2*m) ++ans,mx=mi=x;
		}
		printf("%d\n",ans);
	}
	return 0;
}

C. Virus

首先把所有被病毒分割开的区间算出来,如果不考虑堵上感染的话每天这些区间长度就会减\(2\)

考虑堵上的顺序,一开始以为随便怎么堵对答案都没影响,因为每次总减去的数目不变

后来发现对于长度为1和2的区间,即使堵住了也没法拯救更多的了,因此我们贪心地从长的区间开始堵即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,m,a[N],b[N],cnt,ans;
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<=m;++i) scanf("%d",&a[i]);
		for (sort(a+1,a+m+1),i=1;i<m;++i) b[i]=a[i+1]-a[i]-1;
		b[m]=n-a[m]+a[1]-1; sort(b+1,b+m+1);
		for (ans=cnt=0,i=m;i;--i) if (b[i]-cnt*2>0)
		{
			if (b[i]-cnt*2<=2) ++cnt,++ans; else ans+=b[i]-cnt*2-1,cnt+=2;
		} else break;
		printf("%d\n",n-ans);
	}
	return 0;
}

D. Magical Array

STO YKH ORZ

不难发现这个两种操作都很像差分,因此我们把\(c_i\)的前缀和数组\(s_i\)求出

然后我们发现操作1对\(\sum_{i=1}^m s_i\)的大小是没影响的,而每一次操作2会让\(\sum_{i=1}^m s_i\)的值减1

发现这个性质后就很简单了

#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=300005;
int t,n,m,x,pre,f[N];
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i,j,p,c; for (scanf("%lld%lld",&n,&m),i=1;i<=n;++i)
		for (pre=f[i]=0,j=1;j<=m;++j) scanf("%lld",&x),pre+=x,f[i]+=pre;
		for (i=1;i<n;++i) if (f[i]!=f[i+1]) { p=i; break; }
		for (c=0,i=1;i<=n;++i) if (f[i]==f[p]) ++c;
		if (c==n-1) printf("%lld %lld\n",p+1,f[p]-f[p+1]); else printf("%lld %lld\n",p,f[p+1]-f[p]);
	}
	return 0;
}

E. Count Seconds

我的做法很蠢权当娱乐的说

首先数据范围很小我们考虑暴力,朴素的直接枚举时间的方法肯定不可行

因此我们想到每次找出\(a_i\)不为0且最小的入度为0的点\(p\),然后让时间经过\(a_p\),这样这个点就被永久剔除了

然后写完就发现会出现前面的点值不为0而后面的点值为0导致的堵塞行为(其实就是样例2)

但是YKHdalao敏锐地发现了在经过\(n-1\)次操作后,必然会形成如果点\(i\)满足\(a_i\ne 0\),那么它能到达的所有点\(j\)一定满足\(a_j\ne 0\)的局面,就可以用上面的方法做了

然后我就开始写,随后又发现取模之后我们没法正确的找出最小值了

但是由于我们这里只需要能比较数据的大小关系,不妨把一个数表示成\(A\times 998244353+B(B<998244353)\)的形式

因为\(A\)不参与答案计算,因此直接用long double暴力存下\(A\)大致量级即可完成比较操作

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

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005,mod=998244353;
typedef vector <int>:: iterator VI;
int t,n,m,x,y,in[N],a[N],b[N],vis[N],tar,timm; vector <int> v[N];
struct data
{
	long double p; int r;
	friend inline bool operator < (const data& A,const data& B)
	{
		if (A.p<B.p) return 1; if (A.p>B.p) return 0; return A.r<B.r;
	}
	friend inline data operator + (const data& A,const data& B)
	{
		data C; if (A.r+B.r>=mod) C=(data){A.p+B.p+1,A.r+B.r-mod};
		else C=(data){A.p+B.p,A.r+B.r}; return C;
	}
	friend inline data operator - (const data& A,const data& B)
	{
		data C; if (A.r-B.r<0) C=(data){A.p-B.p-1,A.r-B.r+mod};
		else C=(data){A.p-B.p,A.r-B.r}; return C;
	}
	friend inline data operator * (CI x,const data& B)
	{
		int P=1LL*x*B.r/mod,R=1LL*x*B.r%mod; return (data){x*B.p+P,R};
	}
}c[N],tim;
inline bool check(void)
{
	for (RI i=1;i<=n;++i) if (a[i]) return 1; return 0;
}
inline int add(CI x,CI y)
{
	return x+y>=mod?x+y-mod: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%d",&n,&m),i=1;i<=n;++i)
		scanf("%d",&a[i]),v[i].clear(),in[i]=vis[i]=0;
		for (i=1;i<=m;++i) scanf("%d%d",&x,&y),++in[y],v[x].push_back(y);
		for (i=1;i<=n;++i) if (!v[i].size()) tar=i; timm=0;
		for (;timm<=n&&check();++timm)
		{
			for (i=1;i<=n;++i) b[i]=0;
			for (i=1;i<=n;++i) if (a[i])
			{
				--b[i]; for (VI it=v[i].begin();it!=v[i].end();++it) ++b[*it];
			}
			for (i=1;i<=n;++i) a[i]+=b[i];
		}
		if (!check()) { printf("%d\n",timm); continue; }
		for (i=1;i<=n;++i) c[i]=(data){0,a[i]}; tim=(data){0,timm};
		for (;;)
		{
			if (!in[tar]) { printf("%d\n",add(c[tar].r,tim.r)); break; }
			int p=0; data mi; for (i=1;i<=n;++i)
			if (!in[i]&&!vis[i]) if (!p||c[i]<mi) mi=c[i],p=i;
			for (i=1;i<=n;++i) c[i]=c[i]-mi+(in[i]*mi); tim=tim+mi; vis[p]=1;
			for (VI it=v[p].begin();it!=v[p].end();++it) --in[*it];
		}
	}
	return 0;
}

PS:看了sol发现我就是个睿智,连拓扑排序都想不到

直接把拓扑序求出来之后按序把\(a_i\)加到后面相连的点即可,不过仍然需要前面的\(n-1\)次爆枚来保证没有0点堵塞


F. Colouring Game

咱就是说只能想到RB个数不相等的情况了,后面的什么SG函数那是忘得一干二净

首先我们不难发现当RB的数量不相等时,多的那一方必然获胜

因为首先两个人开始的操作肯定是对着RB或者BR一顿消除,这并不会让两人的颜色数量差改变

接下来当不存在RB或者BR后,以Alice为例,他的操作必然是RW或者WR消除,这样会让他的颜色减少1

在轮流操作的情况下,颜色少的那一方必然会先陷入败局

那么接下来考虑RB个数相等的情况,也就是比每次删一个RBBR,看谁先不能操作,就只能删自己的一个

因此我们不妨把所有RB相间的段提取出来,考虑对每一段独立处理

因为双方的操作集合相同(删的是两个不同颜色),因此可以套SG函数的板子

\(SG(i)=\operatorname{mex}_{j=0}^{i-2} SG(j)\oplus SG(i-2-j)\)

但是这样直接做是\(O(n^2)\)的,通过对不同长度的SG函数打表发现它们在后面存在长度为\(34\)的循环节,因此可以直接跑出前面的然后\(O(n)\)推出后面的

证明参见官方sol

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=500005;
int t,n,SG[N],cur; char s[N]; bool vis[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (i=1;i<=1000;++i)
	{
		for (j=0;j<=i;++j) vis[j]=0;
		for (j=0;j<=i-2;++j) vis[SG[j]^SG[i-2-j]]=1;
		for (j=0;vis[j];++j); SG[i]=j;
	}
	for (i=1001;i<=500000;++i) SG[i]=SG[i-34];
	for (scanf("%d",&t);t;--t)
	{
		for (scanf("%d%s",&n,s+1),cur=0,i=1;i<=n;++i)
		if (s[i]=='R') ++cur; else --cur;
		if (cur>0) { puts("Alice"); continue; }
		if (cur<0) { puts("Bob"); continue; }
		for (cur=0,i=1;i<=n;i=j)
		{
			for (j=i+1;j<=n&&s[j]!=s[j-1];++j); cur^=SG[j-i];
		}
		puts(cur?"Alice":"Bob");
	}
	return 0;
}

Postscript

G和H看了下好像都是几百行的多项式,以现在的代码水平还是润了为好……

posted @ 2022-08-02 11:54  空気力学の詩  阅读(46)  评论(0编辑  收藏  举报