C.C - Tak and Cards
我一开始想的是先从小到大排个序,然后分情况,先从左往右一个数一个数枚举,如果等于ave*1,就res++,如果大于ave*1,就说明1个数的没有了,然后从左到右两个数两个数枚举,如果等于ave*2,就res++,如果大于ave*2,就说明两个数的没有了,以此类推
但是忽略了不一定非得相邻的两个数相加,这个思路漏洞很大,不能采用
实际上,像这种平均数的问题,可以所有数都减去一个平均数,然后在这些数里选择,加起来等于0的,那么它们的原始数的平均数就是该平均数,这里暂且先不这么做
有限制的选择,背包问题
f[i][j][k]表示从前i张卡片中选,选择j张,总和是k的方案数
状态转移:在前i张卡片中选,选择j张,总和是k的方案数可以是选i与不选i的方案数总和
不选i,在前i-1张中选,选择j张,总和为k;
选i,在前i-1张中选,选择j-1张,总和为k-a[i]
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 110;
LL a[N];
LL f[N][N][3000];
LL res;
int main()
{
int n,ave;
cin >> n >> ave;
for (int i = 1; i <= n; i++) cin >> a[i];
f[0][0][0] = 1;//f[0][0][0]=1是因为这一状态用于转移到后面的状态,如果由这一状态转移,肯定要加1的,不可能加0
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= i;j++ ) {
for (int k = 0; k <=2500; k++) {
f[i][j][k] += f[i - 1][j][k];
if (k - a[i] >= 0 && j >= 1) f[i][j][k] += f[i - 1][j - 1][k - a[i]];
}
}
}
for (int j = 1; j <= n; j++) {
res += f[n][j][j*ave];
}
cout << res << endl;
return 0;
}
D.D - Digit Sum
题意:将n转化成b进制,然后对每一位求和,看是否等于s
分类:
1.s等于n,b=n+1
2.s大于n,无解
3.s小于n
a0*b^0+a1*b^1+...+ak*b^k=n
a0+a1+...+ak=s
当k>=2时,b<=sqrt(n),暴力枚举即可
当k=1时,b>sqrt(n),a0*b^0+a1*b^1=n==>a0+a1*b=n
a0+a1=s
所以b=(n-s)/a1+1,因为a1<b,所以a1<=sqrt(n-s)
接着只需要枚举 n - s 的合法性判断是否存在即可
注意合法性判断既要满足 x1,x2 大于等于 0,也要小于 b(x1,x2即n在b进制下第一位和第二位)
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define int long long
int n, s;
bool check(int x) {
int res = 0;
int m = n;
while (m) {
res += m % x;
m /= x;
}
return res == s;
}
signed main()
{
cin >> n >> s;
int sum;
if (s == n) {
cout << n + 1 << endl;
return 0;
}
if (s > n) {
cout << -1 << endl;
return 0;
}
for (int i = 2; i <= n/i; i++) {
if (check(i)){
cout << i << endl;
return 0;
}
}
int ans = 1e18;
int k = n - s;
for (int i = 1; i<=k/i; i++) {
if(k%i==0){
int b = k / i + 1, x0 = s - i;
if (b >= n / b && x0 >= 0 && x0 < b &&i<b&& b >= 2) {
ans = min(ans, b);
}
}
}
if (ans != 1e18) cout << ans << endl;
else cout << -1 << endl;
return 0;
}