CF401
A.Vanya and Cards
题目大意:
给出\(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
题目大意:
共有两类数,一类无限制,二类的数的值必须是一类某数的数值\(+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
题目大意:
给出\(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
题目大意:
\(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题没了,嗨皮恩定