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

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

1.UVA12983 The Battle of Chibi

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

solution

树状数组优化 \(dp\) .

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

转移则有: \(f[i][j] = \displaystyle\sum_{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\n",i,ans);
	}
	return 0;
}

2. UVA11464 Even Parity

给你一个 \(n∗n\) 的 01 矩阵(就是每个元素只可能为 \(0\)\(1\) ),你的任务是把尽量少的 \(0\) 变成 \(1\) ,使得每个元素的上,下,左,右的元素(存在的情况下)之和均为偶数。 \((T\leq 30,n\leq 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\n",t,-1);
		else printf("Case %d: %d\n",t,ans);
	}
	return 0;
}

3.UVA11825 Hackers' Crackdown

我们把每个计算机以及和它相连的计算机看成一个集合 \(p[i]\) ,你需要将 \(n\) 个集合分成尽量多组,使得每一组里面所有集合的并集等于全集。\((n\leq 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\n",step,ans);
	}
	return 0;
}

咕咕咕。

4.UVA10891 Game of Sum

5.UVA10635 Prince and Princess

6.UVA1169 Robotruck

7.UVA1099 Sharing Chocolate

posted @ 2020-11-29 15:14  genshy  阅读(199)  评论(0)    收藏  举报