【2017年 騰訊校招筆試】【數位DP】 + 【解方程 貪心 正難則反】

【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)

*/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章