【总结】三道神一样のdp

拿到题我觉得是两道数论加一道网络流。结果是三道dp!!

orz dp真的好渣啊 感觉快要没救了 

呜呜呜尧神对不起啊真的是考得不好心情很差一大堆负能量_(:з)∠)_


1、稳住GCD

【题目描述】

给你一组数,a1,a2,a3,...,an。
令:G=gcd(a1,a2,a3,...,an)
现在从中任意删除一些数字,设剩下的数为:al1,al2,al3,...,alm。
再令:g=gcd(al1,al2,al3,...,alm)
现要求G=g,问最多能删除多少数?

【输入】

第一行一个数n,第二行n个数a1,a2,a3,...,an。

【输出】

输出只有一个数,表示最多能删除多少数。

【样例输入】

3
4 6 8

【样例输出】

1

【数据范围】

20%的数据,1≤n≤10;
50%的数据,1≤n≤100;
100%的数据,1≤n≤700,1≤ai≤10000;


题解

总感觉想出这个dp的人脑洞很大啊……(不 其实是我题做太少orz)

dp[i][j]表示确定到第i个数 gcd为j时 最少保留数的数目
转移:dp[i][j] = min(dp[i][k] + 1)   其中gcd(a[i], k) == j
然后暴力转移就行了。。。第一维可以去掉变成一个滚动数组(要倒叙枚举j)

考试的时候打了个素数表和一个二进制枚举 骗了50分2333


#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

int read()
{
	int sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

int N, a[705];
int dp[10005];

int gcd(int a, int b)
{
	for(int t = a % b; t; a = b, b = t, t = a % b);
	return b;
}

int main()
{
	freopen("gcd.in", "r", stdin);
	freopen("gcd.out", "w", stdout);
	
	N = read();
	for(int i = 1; i <= N; ++i) a[i] = read();
	
	int G = 0; memset(dp, 0x3f, sizeof(dp)); dp[0] = 0;
	for(int i = 1; i <= N; ++i)
	{
		G = gcd(G, a[i]);
		for(int j = 10000; j >= 0; --j)
		{
			int Gcd = gcd(j, a[i]);
			dp[Gcd] = min(dp[Gcd], dp[j] + 1);
		}
	}
	
	printf("%d\n", N - dp[G]);
	
	return 0;
}

2、骰子游戏

【题目描述】

Analysis和他的伙伴一共N个小朋友一起玩游戏。一开始N个小朋友排成一排,其中An
alysis排在第M位。然后他们按照以下的策略进行游戏:
1. 如果只剩下一个小朋友了,那么他就是赢家;
2. 否则掷一个骰子:
(1) 如果掷到4,那么排在第一位的小朋友胜出;
(2) 如果掷到1,3,5,那么排在第一位的小朋友从队首走到队尾;
(3) 如果掷到2,6,那么排在第一位的小朋友出局,直接离开游戏。
假设骰子均匀,Analysis想知道自己的胜率是多少。

【输入】

一行两个整数N、M,意义如题目描述。

【输出】

一个实数,表示Analysis的胜率。保留9位小数。

【样例输入】

3 2

【样例输出】

0.317460317

【数据范围】

100%的数据,M ≤ N ≤ 1 000。

题解

这道题……挺水的……就是推公式有点麻烦QAQ 然后考试的时候………………没写完…………
本来是打算打个表的 结果手抖了一个地方orz。。。

dp[i][j] 表示剩余i个人 第j个人的胜率
显然 dp[i][1] = 1/6 + 1/2 * dp[i][i]
dp[i][j] = 1/2 * dp[i][j-1] + 1/3 * dp[i-1][j-1]

然后用划一下式子移个项会整出这么个奇葩的东西。。


然后就可以愉快地暴力算了。。。

#include <cstdio>
#include <iostream>

using namespace std;

int read()
{
	int sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

int N, M;
double dp[1005][1005];

double mul(double a, int b)
{
	double temp = 1.0, cmp = a;
	while(b)
	{
		if(b & 1) temp *= cmp;
		b >>= 1;
		cmp *= cmp;
	}
	return temp;
}

int main()
{
	freopen("game.in", "r", stdin);
	freopen("game.out", "w", stdout);
	
	N = read(); M = read();
	
	dp[1][1] = 1.0;
	for(int i = 2; i <= N; ++i)
	{
		double A = 1 - mul(0.5, i), B = 1.0 / 6;
		for(int k = 1; k < i; ++k) B += mul(0.5, k) / 3 * dp[i - 1][i - k];
		dp[i][1] = B / A;
		for(int j = 2; j <= i; ++j) dp[i][j] = 0.5 * dp[i][j - 1] + 1.0 / 3 * dp[i - 1][j - 1];
	}
	
	printf("%.9lf", dp[N][M]);
	
	return 0;
}

3、轻音乐同好会

【题目描述】

雪菜为了能让冬马参加轻音乐同好会,瞒着春希,和冬马见面。为了增进感情,雪菜
拉着还没缓过神来的冬马进了游戏厅……
游戏要求两名玩家在排成一排的石头上跳跃前进,每个石头有一个高度,玩家只能向
右跳,并且不能跳向比自己所在位置的石头矮的石头。一个石头在一个玩家跳离后就会消
失,并且两个玩家不能同时站在同一个石头上。游戏分数为两个玩家站过的石头的总数。
游戏起始,两名玩家都可以任选一个石头作为开始位置(当然不能相同)。
由于冬马是挂科专家,雪菜又只有英语好,所以她们两人想请你帮助他们,怎么才能
让分数最高。

【输入】

第一行一个整数n,表示有n个石头。
第二行n个整数,表示从左到右第i个石头的高度Hi。

【输出】

一个整数,表示最高能得到的分数。

【样例输入】

5
1 7 3 2 4

【样例输出】

4

【数据范围】

30%的数据,2≤n≤200
100%的数据,2≤n≤1000,Hi≤10^9

题解

dp[u][v] 表示一个从i结尾一个从j结尾的最大分数(显然dp[u][v] = dp[v][u])

于是
dp[a[i]][v] = max(dp[u][v]) + 1  (a[i] >= u)   
dp[u][a[i]] = max(dp[u][v]) + 1  (a[i] >= v)

于是对于每个a[i]我们可以找到一个u(或v,其实是等效的) 在a[i] >= u(或v)的条件下 dp[u][a[i]](或dp[a[i]][v])最大
强行转移似乎是n^3的 于是我们可以考虑用线段树或者树状数组来维护
其实我也是今天才知道原来树状数组可以用来维护最大值???orz越来越觉得树状数组高端了。。

先离散化一下(stl就行了 std写的是手工离散化 我只能orz 之前手工离散化每次都把我恶心到) 
然后树状数组下标为a[i]对应的离散化的值 树状数组的值为当前区间的最大值 然后更新的时候向后更新 查找的时候向前查找就可以了
之前lower_bound的时候又手抖把M打成N了 唉毕竟是手残的人生啊

#include <cstdio>
#include <iostream>
#include <algorithm>

#define lowbit(i) (i & -i)

using namespace std;

int read()
{
	int sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

int N, M;
int bfr[1005], aft[1005], f[1005];

struct BIT{
	int dp[1005][1005];
	
	inline int query(int *array, int i)
	{
		int res = 0;
		while(i)
		{
			res = max(res, array[i]);
			i -= lowbit(i);
		}
		return res;
	}
	
	inline void update(int *array, int i, int x)
	{
		while(i <= M)
		{
			array[i] = max(array[i], x);
			i += lowbit(i);
		}
	}
}t;


int main()
{
	freopen("music.in", "r", stdin);
	freopen("music.out", "w", stdout);
	
	N = read();
	for(int i = 0; i < N; ++i)
	{
		bfr[i] = read(); 
		aft[i] = bfr[i];
	}
	sort(aft, aft + N);
	M = unique(aft, aft + N) - aft;
	
	for(int i = 0; i < N; ++i)
	{
		int pos = lower_bound(aft, aft + M, bfr[i]) - aft + 1;
		for(int j = 1; j <= M; ++j) f[j] = t.query(t.dp[j], pos) + 1;
		for(int j = 1; j <= M; ++j)
		{
			t.update(t.dp[j], pos, f[j]);
			t.update(t.dp[pos], j, f[j]);
		}
	}
	
	int ans = -1;
	for(int i = 1; i <= M; ++i) ans = max(ans, t.query(t.dp[i], M));
	
	printf("%d\n", ans);
	
	return 0;
}

然后我写的费用流可以卡过18组 都能在1s内跑完。。只有两组时间是12s…………………………orz

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

int read()
{
	int sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

const int Nmax = 1005;
const int Mmax = Nmax * Nmax;
const int inf = 0x3f3f3f3f; 

int N, H[Nmax]; 
int S, T, SS;

struct ed{
	int v, w, flow, next;
}e[Mmax + 7 * Nmax];
int k, head[Nmax * 2], cur[Nmax * 2];

inline int in(int n) { return n; }
inline int out(int n) { return n + N;}

inline void adde(int u, int v, int w, int flow)
{
	//printf("%d → %d : %d\n", u, v, flow);
	e[k] = (ed) { v, w, flow, head[u] };
	head[u] = k++;
	e[k] = (ed) { u, -w, 0, head[v] };
	head[v] = k++; 
}

bool vis[Nmax * 2];
int dis[Nmax * 2], pre[Nmax * 2], minf;

queue <int> q;

bool spfa()
{
	memset(dis, -0x3f, sizeof(dis));
	minf = inf; dis[S] = 0; q.push(S);
	
	while(q.size())
	{
		int u = q.front(); q.pop(); vis[u] = 0;
		for(int i = head[u]; ~i; i = e[i].next)
		{
			int v = e[i].v;
			if(e[i].flow > 0 && dis[v] < dis[u] + e[i].w)
			{
				dis[v] = dis[u] + e[i].w;
				minf = min(minf, e[i].flow);
				pre[v] = i;
				if(!vis[v]){ q.push(v); vis[v] = 1; }
			}
		}
	}
	
	return dis[T] > 0;
}

inline int max_flow()
{
	int ans = 0;
	while(spfa())
	{
		for(int i = T; i != S; i = e[pre[i] ^ 1].v)
		{
			e[pre[i]].flow -= minf;
			e[pre[i] ^ 1].flow += minf;
		}
		
		ans += dis[T] * minf;
	}
	return ans;
}

int main()
{
	freopen("music.in", "r", stdin);
	freopen("music.out", "w", stdout);
	
	N = read(); memset(head, -1, sizeof(head));
	S = 0, T = (N << 1) + 1, SS = T + 1; adde(S, SS, 0, 2);
	for(int i = 1; i <= N; ++i)
	{
		H[i] = read();
		adde(SS, in(i), 0, inf); adde(out(i), T, 0, inf); adde(in(i), out(i), 1, 1);
		for(int j = 1; j < i; ++j)
		{
			if(H[j] <= H[i]) adde(out(j), in(i), 0, 1);
		}
	}
	
	printf("%d\n", max_flow());
	
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值