CF401

A.Vanya and Cards

CF原题链接

题目大意:

给出\(n\)个数\(a_{i}\),满足\(\lvert a_{i}\rvert \leqslant x\),要求添加若干个满足以上要求的数,使得\(\Sigma a_{i}=0\),求添加数字的最小数量\((1\leqslant n,x\leqslant 1000)\)

解题思路:

直接做做完了。统计一下原本\(\Sigma a_{i}\)的值\(d\)\(\lceil\frac{\lvert d\rvert}{x}\rceil\)就是答案。

小代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3+5;
int n,x;
int a[N];
int sum;

signed main()
{
	scanf("%lld%lld",&n,&x);
	for (int i=1;i<=n;i++) 
	{
		scanf("%lld",&a[i]);
		sum+=a[i];
	}
	if (sum<0) sum=-sum;
	printf("%lld",(sum+x-1)/x);
	return 0;
}

B.Sereja and Contests

CF原题链接

题目大意:

共有两类数,一类无限制,二类的数的值必须是一类某数的数值\(+1\),这些数一起组成\([1,n]\)

题目中给出\(n,k\),给定\(k\)组数据\(op,num1,num2\),若\(op=1\),代表给出的\(num1\)为一类数,\(num2\)为二类数;若\(op=2\),那么不输入\(num2\)\(num1\)为一类数。问剩下未给定的数中最多、最少有几个一类数。\((1\leqslant n,k\leqslant 4000)\)

解题思路:

最多的一类数的个数就是剩下的数的总数,统计一下即可。

最少的一类数一定是连续两个数中存在一个,只需统计剩下的数中连续两个数的个数(这就相当于是二类数的数量),再用最多的一类数的个数减去它就是答案

坏代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e3+5;
int n,k;
bool flag[N];
int ans1,ans2;

signed main()
{
	scanf("%lld%lld",&n,&k);
	for (int i=1;i<=k;i++)
	{
		int op,num1,num2;
		scanf("%lld%lld",&op,&num1);
		if (op==2) flag[num1]=true;
		else
		{
			scanf("%lld",&num2);
			flag[num1]=flag[num2]=true;
		}
	}
	for (int i=1;i<n;i++)
	{
		if (!flag[i]) ans1++;
	}
	ans2=ans1;
	for (int i=1;i+1<n;i++)
	{
		if (flag[i]==false&&flag[i+1]==false)
		{
			ans2--;
			i++;
		}
	}
	printf("%lld %lld",ans2,ans1);
	return 0;
}

C.Team

CF原题链接

题目大意:

给出\(n,m(1\leqslant n,m\leqslant 10^{6})\),要求构造出仅由\(n\)\(0\)\(m\)\(1\)组成,且没有2个连续的\(0\)、没有3个连续的\(1\)的数列。若无法构造,输出\(-1\)

解题思路:

先考虑无法构造的情况。可以发现,数列中最多有\((n+1)\times2\)\(1\),最少有\(n-1\)\(1\),若\(m\)不在这个范围内,那么一定无法构造成功。

接下来考虑"\(011\)"与"\(01\)"。若\(m>n\),那么一定是构造\(011\)更优,若二者相等,那么构造\(01\)更优。若构造完成后有剩余的\(1\),开头特判提前输出即可。

不好的代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m;
string ans;

signed main()
{
	cin>>n>>m;
	if ((n+1)*2<m||m<n-1)
	{
		printf("-1");
		return 0;
	}
	if (m==n*2+2) printf("11"),m-=2;
	if (m==n*2+1) printf("1"),m--;
	while (n<m&&n!=0&&m>0)
	{
		printf("0");
		int cnt1=min(1LL*2,m);
		m-=cnt1;
		while (cnt1--) printf("1");
		n--;
	}
	while (n!=0&&m!=0)
	{
		m--,n--;
		printf("01");
	}
	while (m--) printf("1");
	while (n--) printf("0");
	cout<<ans;
	return 0;
}

D.Roman and Numbers

CF原题链接

题目大意:

\(n\)每个数位上的数字可以重新排列成为新的\(n'\),要求\(n'\)\(m\)的倍数,求方案数。(\(1\leqslant n<10^{18},1\leqslant m\leqslant 100\))

不允许有前导0!

解题思路:

翻开题解发现是状压DP,果断放弃摆烂

于是复习了一下很久以前学的状压,就着几篇题解终于把这题吃了

注意到\(n\)的范围,题解说一眼状压\(dp\)

设状态为\(f_{i,j,k}\)\(i\)为每个数选或不选构成的集合,状压\(dp\)必有的东西;\(j\)表示当前方案%\(m\)\(j\)\(k\)表示目前是否出现了\(0\),所以\(f_{1<<cnt-1,0,1}\)就是最后的答案。(\(cnt\)表示\(n\)的数位位数)

然后好像就没了?

先预处理出\(n\)每一位上的数字\(a_{i}\),因为题目不允许有前导0,所以若\(a_{i}=0\),那么一定要从没有0出现过的状态转移过来;如果不等于0的话,那么就从两种情况转移过来。

状态转移方程(\(a_{j}\neq0\)版),其中\(i\)为状态,\(j\)为枚举的数位,\(k\)为余数

f[i|(1<<j)][(k*10+a[j])%m][1]+=f[i][k][1]+f[i][k][0];

然后就水灵灵地做出来了?

水灵灵的代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5;
int n,m;
int cnt,a[20];
int vis[10];
int f[N][110][2];

signed main()
{
	scanf("%lld%lld",&n,&m);
	while (n)//处理n的数位
	{
		a[cnt++]=n%10;
		n/=10;
	}
	f[0][0][0]=1;
	for (int i=0;i<(1<<cnt);i++)//状态 
	{
		memset(vis,0,sizeof vis);
		for (int j=0;j<cnt;j++)//数位 
		{
			if (i&(1<<j)||vis[a[j]]) continue;//前面的状态出现过或有重复的
			vis[a[j]]=1;
			for (int k=0;k<m;k++)//余数 
			{
				if (a[j])
				{
					f[i|(1<<j)][(k*10+a[j])%m][1]+=f[i][k][0];
					f[i|(1<<j)][(k*10+a[j])%m][1]+=f[i][k][1];
				}
				else f[i|(1<<j)][(k*10+a[j])%m][1]+=f[i][k][1];
			}
		}
	}
	printf("%lld",f[(1<<cnt)-1][0][1]);
	return 0;
}

E题没了,嗨皮恩定

E题被其他比赛拿走啦

posted @ 2024-11-11 11:26  还是沄沄沄  阅读(81)  评论(0编辑  收藏  举报