uva 经典习题选做(dp专项)

都是一些从蓝皮书上找的题。

1.UVA12983 The Battle of Chibi

给你一个数字序列,让你求出长度为 (k) 的最长上升子序列的个数。((nleq 1000,Tleq 100)

solution

树状数组优化 (dp) .

(f[i][j]) 表示以 第 (i) 个数为结尾,且最长上升子序列长度为 (j) 的数目。

转移则有: (f[i][j] = displaystylesum_{k=1}^{i-1} f[k][j-1]) ((a[k] leq a[i])) .

直接做的话复杂度是 (O(Tn^2)) .优化一下复杂度。

考虑到转移只会发生在比 (a[i]) 小的数之间,所以考虑用权值树状数组优化一下。

对每个 (k) 都开一个权值树状数组,转移时查询一下 (a[i]) 得前缀和即可。

答案即为 (f[i][m]).

复杂度 (O(Tnlogn)).

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int p = 1e9+7;
const int N = 1010;
int n,m,T,ans;
int a[N],b[N],f[N][N],tr[N][N];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
int lowbit(int x) { return x & -x;}
void chenge(int x,int now,int val)
{
	for(; x <= n; x += lowbit(x)) tr[now][x] = (tr[now][x] + val) % p;
}
int query(int x,int now)
{
	int res = 0;
	for(; x; x -= lowbit(x)) res = (res + tr[now][x]) % p;
	return res;
}
int main()
{
	T = read();
	for(int i = 1; i <= T; i++)
	{
		n = read(); m = read(); ans = 0;
		memset(tr,0,sizeof(tr));
		memset(f,0,sizeof(f));
		for(int i = 1; i <= n; i++) a[i] = b[i] = read();
		sort(b+1,b+n+1);
		int num = unique(b+1,b+n+1)-b-1;
		for(int i = 1; i <= n; i++) a[i] = lower_bound(b+1,b+num+1,a[i])-b;
		for(int i = 1; i <= n; i++)
		{
			f[i][1] = 1; chenge(a[i],1,1);
			for(int j = 2; j <= m; j++)
			{
				f[i][j] = query(a[i]-1,j-1);
				chenge(a[i],j,f[i][j]);
			}
		}
		for(int i = 1; i <= n; i++) ans = (ans + f[i][m]) % p;
		printf("Case #%d: %d
",i,ans);
	}
	return 0;
}

2. UVA11464 Even Parity

给你一个 (n∗n) 的 01 矩阵(就是每个元素只可能为 (0)(1) ),你的任务是把尽量少的 (0) 变成 (1) ,使得每个元素的上,下,左,右的元素(存在的情况下)之和均为偶数。 ((Tleq 30,nleq 15))

solution

状压 and 递推。

一个很显然的暴力就是枚举每个矩阵最后的状态,然后判断一下每个解是否可行,复杂度 (O(T 2^{格子数})).

一个比较重要的优化:当我们第一行的状态确定的时候,那么整个矩阵也就可以确定出来了。

所以我们只需要枚举第一行的状态,然后判断是否符合条件即可,复杂度 (O(T2^n))

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int inf = 1e8;
const int N = 50;
int T,ans,n,s[N][N],b[N][N];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
int calc(int x)
{
	int sum;
	memset(b,0,sizeof(b));
	for(int i = 1; i <= n; i++)
	{
		int tmp = (x>>(i-1)) & 1;
		if(tmp == 1) b[1][i] = 1;
		else if(s[1][i] == 1) return inf;//0不能变为1
	}
	for(int i = 2; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			sum = 0;
			sum += b[i-2][j];//i-1,j 的上左右四个方位的和。
			sum += b[i-1][j-1];
			sum += b[i-1][j+1];
			b[i][j] = sum % 2;//i-1,j 的下方的数
			if(s[i][j] == 1 && b[i][j] == 0) return inf;//0不能变为1
		}
	}
	int cnt = 0;
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			if(b[i][j] != s[i][j]) cnt++;//计算需要变化多少次。
		}
	}
	return cnt;
}
int main()
{
	T = read();
	for(int t = 1; t <= T; t++)
	{
		n = read(); ans = inf;
		for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) s[i][j] = read();
		for(int i = 0; i < (1<<n); i++)//枚举第一行的状态
		{
			ans = min(ans,calc(i));
		}
		if(ans == inf) printf("Case %d: %d
",t,-1);
		else printf("Case %d: %d
",t,ans);
	}
	return 0;
}

3.UVA11825 Hackers' Crackdown

我们把每个计算机以及和它相连的计算机看成一个集合 (p[i]) ,你需要将 (n) 个集合分成尽量多组,使得每一组里面所有集合的并集等于全集。((nleq 16))

solution

(n) 这么小铁定是状压 (dp).

首先我们先预处理出 (p[i]) 数组。

for(int i = 1; i <= n; i++)
{
	p[i] = (1<<(i-1));//它本身
	x = read();
	for(int j = 1; j <= x; j++)
	{
		int k = read() + 1;
		p[i] |= (1<<(k-1));//与他相连的计算机
	}
}

然后预处理出每个计算机二进制状态的并集 即 (zt[i]).

(f[i]) 表示计算机二进制状态为 (i) (及选或不选) 的情况下,能分的组数最多是多少。

转移则有 (f[i] = max(f[i],f[i) (xor) $ j]+1)$ ((zt[j] = 全集))

枚举一下子集进行转移即可,复杂度大概为 (O(3^n)) 左右。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = (1<<17);
int n,ans,step,x;
int zt[N],f[N],p[20];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
int main()
{
	while(1)
	{
		step++;
		memset(p,0,sizeof(p));
		memset(zt,0,sizeof(zt));
		n = read(); ans = 0;
		if(n == 0) break;
		for(int i = 1; i <= n; i++)
		{
			p[i] = (1<<(i-1));
			x = read();
			for(int j = 1; j <= x; j++)
			{
				int k = read() + 1;
				p[i] |= (1<<(k-1));
			}
		}
		for(int i = 0; i < (1<<n); i++)
		{
			for(int j = 1; j <= n; j++)
			{
				if(i & (1<<(j-1))) zt[i] |= p[j];
			}
		}
		f[0] = 0;
		for(int i = 1; i < (1<<n); i++)
		{
			f[i] = 0;
			for(int j = i; j; j = (j-1) & i)//枚举子集
			{
				if(zt[j] == (1<<n)-1) f[i] = max(f[i],f[i^j]+1);
            }
			ans = max(ans,f[i]);
		}
		printf("Case %d: %d
",step,ans);
	}
	return 0;
}

咕咕咕。

4.UVA10891 Game of Sum

5.UVA10635 Prince and Princess

6.UVA1169 Robotruck

7.UVA1099 Sharing Chocolate

原文地址:https://www.cnblogs.com/genshy/p/14056373.html