都是一些从蓝皮书上找的题。
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;
}
我们把每个计算机以及和它相连的计算机看成一个集合 (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;
}
咕咕咕。