Tenka1 Programmer Contest 2019

C - Stones

枚举分界点爆算即可。


#include<iostream>
#include<cstdio>
using namespace std;
const int N=200005;
int n;
char s[N];
int sum[N][2];
int main()
{
	scanf("%d",&n);
	scanf("%s",s+1);
	sum[0][0]=sum[0][1]=0;
	for(int i=1;i<=n;i++)
	{
		sum[i][0]=sum[i-1][0],sum[i][1]=sum[i-1][1];
		if(s[i]=='.') sum[i][0]++;
		else if(s[i]=='#') sum[i][1]++;
	}
	int ans=n;
	for(int i=0;i<=n;i++)
		ans=min(ans,(i-sum[i][0])+(n-i-(sum[n][1]-sum[i][1])));
	printf("%d",ans);
	return 0;
}

D - Three Colors

考虑计算不合法的方案。

\(f_{i,j}\) 表示考虑前 \(i\) 个数,\(c\) 的集合中的数的和为 \(j\) 的方案数。转移分成分到 \(a,b\) 或者分到 \(c\) 讨论。

如果直接计算会发现 \(m\) 为偶数时 \(c\) 的大小为 \(\frac{m}{2}\) 会被多算,再 DP 一遍减去就好了。


#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=305;
const int MOD=998244353;
int n,m;
int a[N];
long long f[N][N*N],g[N][N*N];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]),m+=a[i];
	f[0][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=m;j++)
		{
			f[i][j]=f[i-1][j]*2%MOD;
			if(j>=a[i]) f[i][j]=(f[i][j]+f[i-1][j-a[i]])%MOD;
		}
	long long ans=1;
	for(int i=1;i<=n;i++)
		ans=ans*3%MOD;
	for(int j=(m+1)/2;j<=m;j++)
		ans=(ans-f[n][j]*3%MOD+MOD)%MOD;
	if(m%2==0)
	{
		g[0][0]=1;
		for(int i=1;i<=n;i++)
			for(int j=0;j<=m;j++)
			{
				g[i][j]=g[i-1][j];
				if(j>=a[i]) g[i][j]=(g[i][j]+g[i-1][j-a[i]])%MOD;
			}
		ans=(ans+g[n][m/2]*3%MOD)%MOD;
	}
	printf("%lld",ans);
	return 0;
}

E - Polynomial Divisors

\(f(i)\equiv 0 \pmod p\) 相当于 \(f(i)\) 在模 \(p\) 意义下存在因式 \((x -i)\)

\(\forall i\in N,f(i)\equiv 0\pmod p\) 等价于 \(f ( i )\) 在模 $p $ 意义下存在因式 \(\prod\limits_{i=0}^{p-1}(x-i)\equiv x^p+\cdots \ +\prod\limits_{i=0}^{p-1}(-i) \pmod p\),因为 \(x\) 的指数需要小于等于 \(n\),故 \(p\)\(a_i\) 的公因数或者 \(p\leq n\)

\(x\neq 0\) 时,有 \(x^{p-1}\equiv 1 \pmod p\),则:

\[\begin{aligned}f(x)&= a_nx^n+a_{n-1}x^{n-1}+\cdots +a_0 \\ &= x^0(a_0+a_{p-1}x^{p-1}+a_{2(p-1)}x^{2(p-1)}+\cdots )+x^1(a_1+a_px^{p-1}+a_{2(p-1)+1}x^{2(p-1)}+\cdots )+\cdots \\ &=x^0(a_0+a_{p-1}+a_{2(p-1)}+\cdots ) + x^1(a_1+a_p+a_{2(p-1)+1}+\cdots ) \cdots \end{aligned} \]

只有当 $a_0+a_{p-1}+a_{2(p-1)}+\cdots ,a_1+a_p+a_{2(p-1)+1}+\cdots ,\ldots $ 均为 \(0\) 时才能满足 \(\forall x,f(x)\equiv 0\pmod p\)

对于 \(x=0\) 的情况,\(f(x)=a_0\),需要满足 \(p|a_0\)

暴力枚举 \(p\)\(O(n)\) 判断是否合法即可。


#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N=10005;
int n;
int a[N];
bool isprime[N];
int prime[N],tot;
void init(int n=10000)
{
	memset(isprime,true,sizeof(isprime));
	isprime[1]=false;
	for(int i=2;i<=n;i++)
	{
		if(isprime[i]) prime[++tot]=i;
		for(int j=1;j<=tot&&i*prime[j]<=n;j++)
		{
			isprime[i*prime[j]]=false;
			if(i%prime[j]==0) break;
		}
	}
	return;
}
bool check(int p)
{
	if(a[0]%p!=0) return false;
	static int sum[N];
	for(int i=0;i<=p-2;i++)
		sum[i]=0;
	for(int i=0;i<=n;i++)
		sum[i%(p-1)]=(sum[i%(p-1)]+a[i])%p;
	for(int i=0;i<=p-2;i++)
		if(sum[i]) return false;
	return true;
}
int main()
{
	init();
	scanf("%d",&n);
	for(int i=n;i>=0;i--)
		scanf("%d",&a[i]);
	int k=a[0];
	for(int i=1;i<=n;i++)
		k=__gcd(k,a[i]);
	k=abs(k);
	vector<int>res;
	for(int i=1;i<=tot&&prime[i]<=sqrt(k);i++)
		if(k%prime[i]==0)
		{
			res.push_back(prime[i]);
			while(k%prime[i]==0) k/=prime[i];
		}
	if(k>1) res.push_back(k);
	for(int i=1;i<=tot&&prime[i]<=n;i++)
	{
		int p=prime[i];
		if(check(p)) res.push_back(p);
	}
	sort(res.begin(),res.end());
	res.erase(unique(res.begin(),res.end()),res.end());
	for(int p:res)
		printf("%d\n",p);
	return 0;
}

Banned X

因为 \(0\) 不会对总和产生影响,考虑枚举 \(0\) 的个数 \(i\) 计算出方案 \(f(n-i)\) 再将 \(0\) 插入。

考虑合法的 \(1,2\) 的串需要满足其中一种情况:

  1. 序列的和不超过 \(X\)
  2. 序列中只有 \(2\)
  3. 序列的和为奇数,序列的中的任意一个 \(1\),它和左边的数的和 \(< X\),它和右边的数的和 \(< X\)

可以发现,序列中一定会有 \(x-1\) 的一段,则剩下的数一定都为 \(2\)

具体证明的话可以发现这段的右边第一个肯定为 \(2\),如果这段的左端点为 \(1\) 的话就不满足条件。故这段的左端点为 \(2\),那么这个区间就可以往右移直到 \(1\) 位置就不合法了。第三种情况的证明同理。

对于第 1,2 种情况,可以用组合数算出。

对于第 3 种情况,序列右侧和左侧都应有至少 \(\frac{S-X+1}{2}\)\(2\),中间可任意摆放。

证明的话可以考虑前缀的 \(2\) 小于后缀的 \(2\) 的情况,我们可以一直向右移动到左边第一个 \(1\),这就不合法了。前缀的 \(2\) 大于后缀的 \(2\) 的情况同理。


#include<iostream>
#include<cstdio>
using namespace std;
const int N=3005;
const int MOD=998244353;
int n,X;
long long ksm(long long a,long long b)
{
	long long res=1;
	while(b)
	{
		if(b&1) res=res*a%MOD;
		a=a*a%MOD,b>>=1;
	}
	return res;
}
long long fac[N],inv[N];
void init(int n=3000)
{
	fac[0]=1;
	for(int i=1;i<=n;i++)
		fac[i]=fac[i-1]*i%MOD;
	inv[n]=ksm(fac[n],MOD-2);
	for(int i=n;i>=1;i--)
		inv[i-1]=inv[i]*i%MOD;
	return;
}
long long C(int n,int m)
{
	if(m>n) return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
long long f(int n)
{
	long long res=0;
	if(2*n<X||(2*n-X)%2==1) res++;
	for(int S=0;S<2*n;S++)
	{
		if(S<X)
		{
			if(S>=n) res=(res+C(n,S-n))%MOD;
		}
		else
		{
			if((S-X)%2==0) continue;
			int num=(S-X+1)/2;
			int reb=n-2*num,ret=S-2*num*2;
			if(reb<=0||ret<=0) continue;
			if(ret>=reb&&ret<=2*reb) res=(res+C(reb,ret-reb))%MOD;
		}
	}
	return res;
}
int main()
{
	init();
	scanf("%d%d",&n,&X);
	long long ans=0;
	for(int i=0;i<=n;i++)
		ans=(ans+C(n,i)*f(n-i)%MOD)%MOD;
	printf("%lld",ans);
	return 0;
}
posted @ 2023-09-27 19:11  Zhou_JK  阅读(23)  评论(0)    收藏  举报