欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

2019.08.01比赛总结

程序员文章站 2022-03-14 19:20:50
...

本文放代码仅供已做出本题的人交流参考使用,禁止抄袭!!!

今天比赛凉了~~~

Total Mark:18.2+0+100=118.2(\text{Total Mark:18.2+0+100=118.2(}最后一题把Online Judge\text{Online Judge}给卡爆了,到现在还在Pending)\text{Pending})

【题解】

T1:\text{T1:}比赛时觉得是一道博弈论的题目,然而却给出了整个矩阵,所以和其它的博弈论题目不一样,赛时没有思路,于是判断如果整个矩阵的数全是偶数且n\text{n}也为偶数,则输出L\text{L},否则输出W(\text{W(}竟然还有18.2pts!!!)\text{18.2pts!!!)}。正解是DP(\text{DP(}我感觉是递推)\text{)},设fi,jf_{i,j}表示前iijj列的矩阵是否存在必胜策略,那么易得如果fi1,jf_{i-1,j}必败,且第ii行前jj个数的和是偶数,则fi,jf_{i,j}必胜。对于fi,j1f_{i,j-1}同理。
:对于一段数的和需使用二维前缀和sumi,j=sumi1,j+sumi,j1sumi1,j1+ai,jsum_{i,j}=sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-1}+a_{i,j},一开始判断如果a1,1a_{1,1}是偶数,则f1,1f_{1,1}必胜,否则必败。

代码:

#include<cstdio>
#include<cstring>
#define N 1010
using namespace std;
int t,n;
int f[N][N],sum[N][N],a[N][N];
int main()
{
	scanf("%d",&t);
	++t;
	while (--t)
	{
		memset(sum,0,sizeof sum);
		memset(f,0,sizeof f);
		scanf("%d",&n);
		for (int i=1;i<=n;++i)
			for (int j=1;j<=n;++j) 
				scanf("%d",&a[i][j]),sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+a[i][j];
		if (a[1][1]%2==0) f[1][1]=1;
		for (int i=1;i<=n;++i)
			for (int j=1;j<=n;++j)
			{
				if (!f[i-1][j] && (sum[i][j]-sum[i-1][j])%2==0) f[i][j]=1;
				if (!f[i][j-1] && (sum[i][j]-sum[i][j-1])%2==0) f[i][j]=1;
			}
		if (f[n][n]) printf("W\n");
		else printf("L\n");
	}
	return 0;
}

T2:\text{T2:}比赛时没有思路,对于如何判断相邻的数不知所措,所以最后提交了输出rand()%5+1\text{rand()\%5+1}的程序,果不其然拿了0\text{0}分。正解是暴力模拟,用一个c\text{c}数组,ci,jc_{i,j}表示与编号为ii的数相邻的第jj个数是什么,首先要预处理出ansans数组,其中有几个点要注意,要前后连相邻边,每一层的环要首尾连相邻边,如果你在判外层相邻的时候先自增了nownow,那么最后记得1-1

代码:

#include<cstdio>
#include<cstring>
#define N 10010
using namespace std;
int n,a,begin,end,t,now;
int c[N][10],cnt[N],tot[N],bz[10],cc[N];
inline int getnum()
{
	int o=0x3f3f3f3f,w;
	for (int I=1;I<=5;++I) if (o>tot[I] && !bz[I]) o=tot[I],w=I;
	return w;
}
void getans()
{
	begin=2,end=7,t=2;
	tot[1]=cc[1]=1;
	for (int i=2;i<=7;++i) c[1][++cnt[1]]=i,c[i][++cnt[i]]=1;
	for (int i=2;i<=10005;++i)
	{
		c[i][++cnt[i]]=i+1;
		c[i+1][++cnt[i+1]]=i;
		memset(bz,0,sizeof bz);
		for (int ii=1;ii<=cnt[i];++ii) bz[cc[c[i][ii]]]=1;
		cc[i]=getnum();
		++tot[cc[i]];
		if (i==begin) c[i][++cnt[i]]=end,c[end][++cnt[end]]=i,begin=end+1,end+=t*6,++t,now=begin;
		while (cnt[i]<6) c[i][++cnt[i]]=now,c[now][++cnt[now]]=i,++now;
		--now;
	}
}
int main()
{
	memset(c,0,sizeof c);
	memset(cnt,0,sizeof cnt);
	memset(tot,0,sizeof tot);
	memset(cc,0,sizeof cc);
	scanf("%d",&n);
	getans();
	while (n--)
	{
		scanf("%d",&a);
		printf("%d\n",cc[a]);
	}
	return 0;
}

T3:\text{T3:}比赛时一开始自然地想先拿暴力分,打了个30pts\text{30pts}的暴力。后来被“余数”二字开导,想到用一个桶存该序列的前缀和%k\%k的余数,因为在同一个桶中的数两两为左右端点都可以组成一个合法的子序列,即第ii个桶的贡献是Ccnti2C_{cnt_i}^2cnticnt_i表示第ii个桶中数的个数,证明如下:

11ii的前缀和%k\%k的值为p\text{p}
11j(j&gt;i)j(j&gt;i)的前缀和%k\%k的值也为p\text{p}
则原序列中sum(i,j)%ksum(i,j)\%k的值为00
因为nn个数中取出22个数的组合种数是Cn2C_n^2,所以第ii个桶的贡献是Ccnti2C_{cnt_i}^2

:最后的答案要加上cnt0cnt_0,因为对于前缀和能整除kk的,自己也能为一个子序列。

十年OI\text{OI}一场空,不开long long\text{long long}见祖宗

代码:

#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
ll t,k,n,a,now,ans;
ll cnt[1000010];
int main()
{
	scanf("%lld",&t);
	while (t--)
	{
		memset(cnt,0,sizeof cnt);
		now=ans=0;
		scanf("%lld %lld",&k,&n);
		for (int i=1;i<=n;++i) scanf("%lld",&a),now=(now+a)%k,cnt[now]++;
		for (int i=0;i<k;++i) ans+=cnt[i]*(cnt[i]-1)/2;
		printf("%lld\n",ans+cnt[0]);
	}
	return 0;
}

明天继续加油!!!

相关标签: 比赛总结