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;
}
我们把每个计算机以及和它相连的计算机看成一个集合 \(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;
}
咕咕咕。

浙公网安备 33010602011771号