标签: dp - CheaSim Blog

DNA Sequence

DNA Sequence

题意

给定$m$个指定串,寻找长度为$n$的不含指定串的字符串。

题解

AC自动机+dp+矩阵快速幂。

如果要不含病毒串,那么我们相当于在每个状态中不能指向那个病毒终点串。对于每个状态来说,可以选择的就是4个字母去掉下一个状态是病毒的字母。

$dp[i] += dp[j] (j是非病毒终点)$

初始化$dp[i]$为可以选择的字母。相当于$i$状态加上状态$j$乘上字母数。可以构造一个矩阵mx。$mx[i][j]$表示$i$到$j$的字母数,矩阵的$n$次方之后就是经过$n$个状态转化后的值。

举个例子,比如我们是AC,AT,AG,AA这四个病毒串,那么root = 0,A是1,C=2,T=3,A=4。有五个状态。

当我们状态为0的时候,有两种选择。我们到状态0的选择字母可以选3个’C’’T’’G’。到状态1可以选1个’A’。之后到状态1没有选择了,因为全是病毒串。所以我们的矩阵是

3 1

0 0

之后矩阵乘就完事了。

我感觉讲得有点不清楚。。。

+2 TLE因为矩阵的大小是有build的trie树决定的,所以越小越好用ac.tot来表示矩阵的大小,也就是状态的大小。

+10 矩阵的大小其实是状态的大小,状态的大小是 输入的病毒串长度*病毒串的次数。最大tot为这么大。

+1 因为转移的状态只有四种,所以maxson为4就好了,大了的话创造矩阵的时候会出现问题。

###ac代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#include<queue>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxson = 4;
const ll mod = 100000;
int n,m;
const int MAX = 110;
int tot = 0;
struct Matrix{
ll mx[MAX][MAX];
int n;
Matrix(){memset(mx,0,sizeof(mx)); n = MAX;}
Matrix operator +(const Matrix &b)const{
Matrix res;
rep(i,0,MAX) rep(j,0,MAX)
res.mx[i][j] = (mx[i][j]+b.mx[i][j])%mod;
return res;
}
Matrix operator *(const Matrix &b) const{
Matrix res;
rep(i,0,tot) rep(j,0,tot) {
rep(k,0,tot){
res.mx[i][j] += mx[i][k]*b.mx[k][j];
}
res.mx[i][j] %= mod;
}
return res;
}
void debug(){
rep(i,0,n){
rep(j,0,n){
cout<<mx[i][j]<<' ';
}
cout<<endl;
}
}
}U,V;
Matrix pow3(Matrix f,int n){
Matrix res;
rep(i,0,MAX) res.mx[i][i] = 1;
while(n){
if(n&1) res = res*f;
n>>=1;
f = f*f;
}
return res;
}
struct Trie{
int next[20*20][maxson],fail[20*20],flag[20*20];
int root,tot;
int encode[256];
int newnode(){
rep(i,0,maxson) next[tot][i] = -1;
flag[tot++] = 0;
return tot-1;
}
void init(){
tot = 0;
root = newnode();
encode['A'] = 0;
encode['T'] = 1;
encode['C'] = 2;
encode['G'] = 3;
memset(flag,0,sizeof(flag));
}
void insert(char *s,int id){
int len = strlen(s);
int now = root;
rep(i,0,len){
int k = encode[s[i]];
if(next[now][k] == -1)
next[now][k] = newnode();
now = next[now][k];
}
flag[now] = id;
}
void build(){
queue<int> q;
fail[root] = root;
rep(i,0,maxson){
if(next[root][i] == -1){
next[root][i] = root;
}else{
fail[next[root][i]] = root;
q.push(next[root][i]);
}
}
while(!q.empty()){
int now = q.front(); q.pop();
if(flag[fail[now]]) flag[now] = 1;
rep(i,0,maxson){
if(next[now][i] == -1){
next[now][i] = next[fail[now]][i];
}else{
fail[next[now][i]] = next[fail[now]][i];
q.push(next[now][i]);
}
}
}
}
Matrix build_mx(){
Matrix res;
rep(i,0,tot) rep(j,0,maxson){
if(flag[i] != 1 && flag[next[i][j]] != 1 ){
res.mx[i][next[i][j]] ++;
}
}
return res;
}
void debug(){
for (int i = 0; i < tot; i++){
printf("id = %3d,fail = %3d,end = %3d,chi = [", i, fail[i], flag[i]);
for (int j = 0; j < maxson; j++) printf("%2d", next[i][j]);
printf("]\n");
}
}
}ac;
char s[30];
int main(){
#ifdef LOCAL
freopen("3.in","r",stdin);
#endif // LOCAL
scanf("%d%d",&n,&m);
ac.init();
rep(i,0,n){
scanf("%s",s);
ac.insert(s,1);
}
ac.build();
tot = ac.tot;
Matrix res1 = ac.build_mx();
Matrix res = pow3(res1,m);
ll ans = 0;
res1.debug();
rep(i,0,ac.tot){
ans += res.mx[0][i];
}
ans = ans % mod;
printf("%d\n",ans);

return 0;
}

[dp+graph] CodeForces - 721C

[dp+graph] CodeForces - 721C

题意

给定一个有向无环图DAG,之后问从点$1$走到点$n$中,在一定的费用要求下,最多能经过多少个点。并给出经过的点。

题解

$n \leq 5000$

我们可以考虑一下二维dp,定义一个dp数组

  • $dp[i][j]$代表着在$i$这个点到终点,经过$j$个点所需要花费的最少时间。

之后利用dfs进行更新。因为是深度优先,所以经过的点不用在遍历一遍。

对于记录经过的点,可以用一个二维数组代表,第$j$个点的时候下一个点是什么。


错误的思想。 WA14

定义两个数组,一个记录已知这个点到达$n$最少耗-费的时间,一个记录已知这个点到达$n$最多的点数。之后利用这两个数组进行剪枝,但是已知wa14的点,我也不知道为什么。可能是dfs的顺序有关。

ac代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 5e3+10;
int n,m,T,cnt;
int head[maxn],vis[maxn];
struct node{
int to,next;
ll val;
}G[maxn];
void init(){
memset(head,-1,sizeof(head));
cnt = 1;
}
void addedge(int u,int v,int val){
G[cnt].to = v;
G[cnt].val = val;
G[cnt].next = head[u];
head[u] = cnt++;
}
int dp[maxn][maxn], bef[maxn][maxn];
void dfs(int u){
vis[u] = 1;
if(u==n) return;
for(int i=head[u];~i;i=G[i].next){
int v = G[i].to;
if(vis[v]==0) dfs(v);
rep(j,1,n){
if(dp[v][j] + G[i].val < dp[u][j+1]){
dp[u][j+1] = dp[v][j] + G[i].val;
bef[u][j+1] = v;
}
}
}
}
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
scanf("%d%d%d",&n,&m,&T);
init();
memset(dp,0x3f,sizeof(dp));
rep(i,1,m+1){
int u,v,val; scanf("%d%d%d",&u,&v,&val);
addedge(u,v,val);
}
dp[n][1] = 0;
dfs(1);
int idx = 1;
per(i,1,n+1){
if(dp[1][i] <= T){
printf("%d\n",i);
idx = i;
break;
}
}
printf("1 ");
int cnt = 1;
while(idx > 1){
printf("%d ",bef[cnt][idx]);
cnt = bef[cnt][idx--];
}
return 0;
}

[cf706C]Hard Problem

706C - Hard problem

题意

题意很简单,就是给定$n$串字符串,对每一串字符串只有一种操作,翻转。之后每翻转一个字符串需要消耗$c_i$的能量,问至少需要多少能量是的,这$n$个字符串是以字典序排列的。

ps:相等也算按字典序

题解

dp,定义一个二维数组$dp[i][j]$。其中$i$表示第$i$个字符串中选择$j$产生字典序的最少能量消耗。$j$只有1和0,表示翻转或者不翻转,之后就是四种情况下去。具体可以看代码.

ac代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head

ll n,m,q;
ll d;
const int maxn = 210;
ll num[maxn];
ll dp[maxn][12][22];
ll a[maxn];
ll MOD(ll x,ll mod){
ll tx = x % mod;
if(tx<0) tx += mod;
return tx;
}
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
int T; scanf("%d",&T);
rep(test_case,1,T+1){
scanf("%lld%lld",&n,&q);
rep(i,1,n+1) scanf("%lld",num+i);
printf("Case %d:\n",test_case);
while(q--){
scanf("%lld%lld",&d,&m);
rep(i,1,n+1) a[i] = MOD(num[i],d);
memset(dp,0,sizeof(dp));
rep(i,0,n) dp[i][0][0] = 1;
rep(i,1,n+1){
rep(j,1,m+1){
rep(k,0,d) dp[i][j][k] = dp[i-1][j][k];
rep(k,0,d){
dp[i][j][(k+a[i])%d] += dp[i-1][j-1][k];
}
}
}
printf("%lld\n",dp[n][m][0]);
}
}
return 0;
}

Codeforces 833B - The Bakery

Codeforces 833B - The Bakery

题意

将一段数字分成最多50个区间,每个区间的价值是区间内不同数字的个数,问怎么样分区间使得价值总和最大。

题解

$dp$加线段树。

$dp[i][j]$表示第$j$个坐标分成$i$块最大的价值。

转移方程

  • $dp[i][j] = max(dp[i][j],dp[i-1][x] + restofx)$,其中$x$是从1到$j$。

因为每次要获得剩下的x中数字不同的个数,暴力的做法是$O(n^2*k)$,

ac代码

[hdoj6447] YJJ's Salesman

[hdoj6447] YJJ’s Salesman

题意

给定一个地图,只能向右或者向上走,地图上有很多点,有以下条件

  • 每个点有钱$val$
  • 你只能从该点的左下方进入点才可以拿钱。

问最多能获得多少钱。

题解

dp + 树状数组优化

单dp就是$O(n^2)$的复杂度。但是题目数据范围$1e5$所以不行。只有树状数组优化了。

树状数组处理的是$dp[i]$代表着以$i$点为重点的权值。

并且由于$x,y$范围是$1e9$所以必须离散化。

有一步我看了好久代码才看懂。在dp方程转移的时候,先对于$x$进行排列,之后在之后更大的$x$再更新之后的dp数组,保证每一次update的dp数组都是最优的。

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 1e5 + 10;
struct node{
int x,y,val;
bool operator <(const node& t)const{
return x < t.x;
}
}a[maxn];
int T,n,tot;
int bit[maxn],dp[maxn];
int lowbit(int x){
return x&-x;
}
void update(int x,int val){
for(int i=x;i<=tot;i+=lowbit(i)) bit[i] = max(bit[i],val);
}
int query(int x){
int s = 0;
for(int i=x;i;i-=lowbit(i)){
s = max(s,bit[i]);
}
return s;
}
int main(){
#ifdef LOCAL
freopen("2.in","r",stdin);
#endif
scanf("%d",&T);
while(T--){
vector<int> v;
memset(bit,0,sizeof(bit));
scanf("%d",&n);
rep(i,0,n){
int x,y,v;scanf("%d%d%d",&x,&y,&v);
a[i].x = x;a[i].y = y;a[i].val = v;
}
rep(i,0,n) v.push_back(a[i].y);
sort(v.begin(),v.end());
sort(a,a+n);
v.erase(unique(v.begin(),v.end()),v.end());
tot = v.size();
rep(i,0,n) a[i].y = lower_bound(v.begin(),v.end(),a[i].y) - v.begin() + 1;
rep(i,0,n) dp[i] = a[i].val;
int ans = 0;int pos = 0;
rep(i,0,n){
while(pos < i && a[pos].x < a[i].x){
update(a[pos].y,dp[pos]);
pos ++;
}
dp[i] = query(a[i].y-1) + a[i].val;
ans = max(ans,dp[i]);
}
printf("%d\n",ans);
}
return 0;
}