最长上升子序列(lis)
问题描述
给出一个数列
{An}
{
A
n
}
,找出其中最长上升子序列的长度。上升子序列中前一
个数严格小于后一个数。
数据范围
20%
20
%
数据满足
n≤1000
n
≤
1000
。
50%
50
%
数据满足
Ai≤100000
A
i
≤
100000
100%
100
%
数据满足
n≤100000
n
≤
100000
,
Ai≤109
A
i
≤
10
9
解题法
解法多种多样。
树状数组、线段树、平衡树维护或者二分单调栈。
代码
#include<bits/stdc++.h>
#define FN "lis"
const int maxn=1e5+5;
int a[maxn];
int minn[maxn];
int main() {
freopen(FN".in","r",stdin);
freopen(FN".out","w",stdout);
int n;
scanf("%d",&n);
memset(minn,0x3f,sizeof(minn));
for(int i=1;i<=n;i++)
scanf("%d",a+i);
minn[1]=a[1];
int now=1;
for(int i=2;i<=n;i++) {
if(a[i]>minn[now])
minn[++now]=a[i];
else {
int pos=std::lower_bound(minn+1,minn+now+1,a[i])-minn;
minn[pos]=a[i];
}
}
printf("%d\n",now);
return 0;
}
隔离村庄(isolate)
问题描述
有n个村庄,通过n-1条双向路连通。Jyb同学财大气粗,想要买其中的恰好k个村庄。为了做一些秘密的事情,他想要通过破坏原来的路,在保证自己的k个村庄相互连通的情况下,把这k个村庄与其他村庄隔离开。请问他应该买哪k个村庄,最少破坏多少条路呢?
数据范围
20% 数据满足n≤10。
100% 数据满足k≤n≤150
解题法
我们保留的k个村庄一定是一棵减掉某些枝的子树,所以只需要求出每个子树保留k个所需要破坏的边数,再枚举选择哪一个子树就好了。
所以设
dp[i][j]
d
p
[
i
]
[
j
]
表示第i个节点的子树,在保留j个节点的情况下最少需要破坏几条边了。
在树上转移的时候,对于每个子节点,可以选择切掉相连的边,答案+1,也可以枚举子节点保留多少个点,选择最小的那个。
具体转移方程:
fori=k..1
f
o
r
i
=
k
.
.
1
dp[u][i]=min(dp[u][i]+1,min(dp[v][i−j]+dp[u][j])0<j<i)
d
p
[
u
]
[
i
]
=
m
i
n
(
d
p
[
u
]
[
i
]
+
1
,
m
i
n
(
d
p
[
v
]
[
i
−
j
]
+
d
p
[
u
]
[
j
]
)
0
<
j
<
i
)
初始条件
dp[u][1]=0
d
p
[
u
]
[
1
]
=
0
最后更新答案时,对于根节点,破坏的边数就是
dp[u][k]
d
p
[
u
]
[
k
]
。对于非根节点,如果要选择它所代表的子树,那么要切掉它的父边,所以破坏的边数是
dp[u][k]+1
d
p
[
u
]
[
k
]
+
1
。
代码(std)
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<algorithm>
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define sz(x) (int)((x).size())
#define ms(x, y) memset(x, y, sizeof(x))
#define pb push_back
using namespace std;
const int N = 155;
vector<int> e[N];
int dp[N][N];
int n, m;
void dfs(int u, int p) {
int len = sz(e[u]);
dp[u][1] = 0;
rep(tv, 0, len - 1) {
int v = e[u][tv];
if(v == p)continue;
dfs(v, u);
for(int i = m; i >= 1; i--) {
int tmp = dp[u][i] + 1;
for(int j = 1; j < i; j++)
tmp = min(tmp, dp[v][i-j] + dp[u][j]);
dp[u][i] = tmp;
}
}
}
int main() {
freopen("isolate.in", "r", stdin);
freopen("isolate.out", "w", stdout);
while(scanf("%d%d", &n, &m)!=EOF) {
int u, v;
ms(dp, 30);
rep(i, 1, n) e[i].clear();
rep(i, 1, n-1){
scanf("%d%d", &u, &v);
e[v].pb(u);
e[u].pb(v);
}
dfs(1, 0);
int ans = dp[1][m];
rep(i, 2, n)
ans = min(ans, dp[i][m] + 1);
printf("%d\n", ans);
}
return 0;
}
多米诺骨牌(domino)
有一个r * c的矩形,和一些1*2的多米诺骨牌。Jyb想用这些骨牌刚好填满这个矩形,使得没有位置是空出来的,多米诺骨牌也没有重叠。请问jyb有多少种方法刚好填满这个矩形呢?
设定矩形是有方向的,旋转之后相同和相互对称的填法应当计算为不同的填法。
r,c⩽11 r , c ⩽ 11
解题法
有够裸的
状压/轮廓线均可。
还可以打表。
代码
我只留了打表程序,留std代码。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<algorithm>
#include<stack>
#include<iostream>
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define sz(x) (int)((x).size())
#define ms(x, y) memset(x, y, sizeof(x))
#define pb push_back
#define ll long long
using namespace std;
int r, c;
ll f[13][2050];
bool ok(int x) {
int z = 0;
rep(i, 1, c) {
if (x & 1) {
if(z & 1)
return false;
}
else
z ^= 1;
x >>= 1;
}
if (z & 1) return false;
return true;
}
int main() {
freopen("domino.in","r",stdin);
freopen("domino.out","w",stdout);
scanf("%d%d", &r, &c);
if((r & 1) && (c & 1))
printf("0\n");
else {
ms(f, 0);
int m = (1 << c) - 1;
rep(i, 0, m)
if(ok(i))
f[1][i] = 1;
rep(i, 2, r)
rep(j, 0, m)
rep(k, 0, m) // 1 in k means vertical dominoes
if( (!(j & k)) && ok(j | k)) //0 in j | k means horizontal dominoes
f[i][k] += f[i-1][j];
cout << f[r][0] << endl;
}
return 0;
}
凑硬币(coin)
问题描述
jyb想给他的女朋友买一块很贵的巧克力,jyb有n个硬币,第i个硬币的价值为ci元,巧克力的价格为k。jyb想用一些硬币凑出恰好k元为他的女朋友买一块巧克力。
看着他的硬币,他想到了一个问题,假设他不给女朋友巧克力,而是给她k元的硬币,她用这恰好k元的硬币可以凑出哪些价值呢?他想不出来,于是请你帮他解答这个问题。
更准确地说,jyb想要知道所有的价值x,在jyb的硬币中能够找出一个恰好k元的子集S,使得存在S的子集S2,S2硬币价值的和是x。显然0和k都是符合条件的x。
n, k <= 500, ci <= 50
解题法
jyb怎么可能有女朋友!
如果去掉先凑出k这个条件,那么直接设F[i]表示i能否被凑出来,枚举硬币转移即可。
加上这个条件后,需要再加一个维度,
F[i][j]
F
[
i
]
[
j
]
表示已经选择的硬币的面值和为i的情况下,j是否能被凑出来。
转移方程如下:
if(f[j][t]==true)
i
f
(
f
[
j
]
[
t
]
==
t
r
u
e
)
f[j+c[i]][t+c[i]]=true;
f
[
j
+
c
[
i
]
]
[
t
+
c
[
i
]
]
=
t
r
u
e
;
f[j+c[i]][t]=true;
f
[
j
+
c
[
i
]
]
[
t
]
=
t
r
u
e
;
最后答案就在
F[k]
F
[
k
]
中(
F[k][t]
F
[
k
]
[
t
]
为true则可输出),输出即可。
代码
#include<bits/stdc++.h>
#define FN "coin"
const int maxn=500+2;
int n,m;
int c[maxn];
int a[maxn],ans;
bool f[maxn][maxn]={true};
int main() {
freopen(FN".in","r",stdin);
freopen(FN".out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",c+i);
for(int i=1;i<=n;i++)
for(int j=m-c[i];j>=0;j--)
for(int k=j;k>=0;k--)
if(f[j][k]) {
f[j+c[i]][k+c[i]]=true;
f[j+c[i]][k]=true;
}
for(int i=0;i<=m;i++)
if(f[m][i])
ans++;
printf("%d\n",ans);
for(int i=0;i<=m;i++)
if(f[m][i])
printf("%d ",i);
return 0;
}
总结
今天Rank更高?
T3差点凉了,还好打了表。
事先不知道这是DP专场,做题就很难受。
有一个巨佬同学假算法T4 80分。
jyb去年出的题依然阴魂不散。
今天没啥大问题。