【2017年 腾讯校招笔试 A】【数位DP】2的幂次方的数字各2个,问你构成n的不同方案数
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x, y) memset(x, y, sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; }
const int N = 1e6 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
LL n;
int f[N];
void table()
{
f[0] = 1;
for (int i = 0; i < 20; ++i)
{
int x = 1 << i;
for (int j = 1e5; j >= 0; --j)if(f[j])
{
f[j + x] += f[j];
f[j + x + x] += f[j];
}
}
//for (int i = 0; i < 100; ++i)
{
//printf("%d %d\n", i, f[i]);
}
}
int TOP;
LL ANS;
void dfs(int p, bool use)
{
if (p > TOP)
{
if (!use)++ANS;
return;
}
if (n >> p & 1)
{
dfs(p + 1, 0);
if (use)dfs(p + 1, 1);
}
else
{
if (!use)dfs(p + 1, 0);
dfs(p + 1, 1);
}
}
LL DFS()
{
ANS = 0;
TOP = -1;
for (int i = 0; i < 63; ++i)if (n >> i & 1)TOP = i;
dfs(0, 0);
return ANS;
}
int d[64][2];
int dp(int p, bool need)
{
if (p == -1)return need == 0;
if (~d[p][need])return d[p][need];
auto &rtn = d[p][need];
rtn = 0;
if (need)
{
rtn += dp(p - 1, 1);
if (~n >> p & 1)rtn += dp(p - 1, 0);
}
else
{
rtn += dp(p - 1, 0);
if (n >> p & 1)rtn += dp(p - 1, 1);
}
return rtn;
}
LL DP()
{
MS(d, -1);
TOP = -1;
for (int i = 0; i < 63; ++i)if (n >> i & 1)TOP = i;
return dp(TOP, 0);
}
int main()
{
table();
while(~scanf("%lld", &n))
{
//printf("%lld\n", DFS());
printf("%lld\n", DP());
//printf("%lld\n", f[n]);
}
return 0;
}
/*
【trick&&吐槽】
2333 环境变了 可不要懵逼啊,沉着冷静才能想出正解~
【题意】
1 2 4 8 16 ... 这些2的幂次方价值的货币各有2个(这2个货币认定为相同的)
问你让用这些数构成n(1e18)的方案数
【分析】
这里DP只能做到1e6
爆搜大概可以做到1e15
但是数位DP可以做到1e10000000
我们用f[i][j]表示我们处理到第i位(比较低的第i位),之前高位是否需要一个数来补上的need = j的方案数
可以做数位DP做转移就好啦——
【时间复杂度&&优化】
O(位数 * 2)
*/
【2017年 腾讯校招笔试 B】【解方程 贪心 正难则反】每次操作加一或乘二,最小步数使得a变A且b变B
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x, y) memset(x, y, sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; }
const int N = 0, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
int a, b, A, B;
int solve(int a, int b, int A, int B)
{
if (a > A || b > B)return -1;
if (a == b)
{
if (A != B)return -1;
//考虑从A到a的路径会更容易
int ans = 0;
while (A != a)
{
++ans;
if (A % 2 == 0 && A >= a * 2)A /= 2;
else --A;
}
return ans;
}
else
{
int MU = (A - B);
int ZI = (a - b);
if (MU % ZI)return -1;
int K = MU / ZI;
int W = A - K * a;
if (K <= 0 || W < 0)return -1;
for (int k = K, cnt = 0; k; k >>= 1)
{
cnt += k & 1;
if (cnt > 1)return -1;
}
//接下来我们考虑如何走B
int ans = 0;
for (int k = 2; k <= K; k *= 2)
{
++ans;
if (W & 1)++ans;
W >>= 1;
}
return ans + W;
}
}
int f[202][202];
int DP()
{
MS(f, 63);
f[a][b] = 0;
for (int i = a; i <= A; ++i)
{
for (int j = b; j <= B; ++j)
{
gmin(f[i + 1][j + 1], f[i][j] + 1);
gmin(f[i * 2][j * 2], f[i][j] + 1);
}
}
if (f[A][B] == inf)f[A][B] = -1;
return f[A][B];
}
int TOP = 100;
int main()
{
//while(~scanf("%d%d%d%d", &a, &b, &A, &B))
while (A = rand() % TOP + 1, B = rand() % TOP + 1, a = rand() % A + 1, b = rand() % B + 1, 1)
{
int ans1, ans2;
printf("%d\n", ans1 = solve(a, b, A, B));
/*printf("%d\n", ans2 = DP());
if (ans1 != ans2)
{
while (1);
}*/
}
return 0;
}
/*
【trick&&吐槽】
听说是topcoder的题,确实蛮难啊2333
【题意】
给你数字a与b和数字A与B
我们每次可以同时操作a与b,使得数值+1,或者使得数值*2
问你最少的操作步数,使得同时使得a变成A,以及b变成B
【分析】
我们假设最优路径是 (((加若干次)乘若干次)加若干次)乘若干次……这样子
那么——
我们可以把式子拆开:
会发现,依然可以得到:A == a * K + W && B == b * K + W(如果有解的话)
得到K = (A - B)/(a - b),W = A - a * K;
当然,这里要先对a == b的情况做特判(详见代码)
然后,我们得到了唯一的(K,W),应该有——
A = K * a + W
B = K * b + W
虽然W可以被折叠起来,但是K是定的,于是K一定要是2^k才行,否则GG
接下来所剩下的,是针对以求得的{K, W}求解。
此时,不妨从低位向高位开始考虑——
如果最低位是1,则表示现在还未进行的*2操作前,需要额外+1,否则,我们可以先*2,这个加1尽量推迟做。
于是,我们一样可以在log的时间复杂度内,可以处理得到{K, W}的解(详见代码)
【时间复杂度&&优化】
O(logA)
*/