dp专题

2019-08-22

21:59:38

小A买彩票

题目链接:https://ac.nowcoder.com/acm/contest/549/C

今天无聊逛牛客,找到了这道简单dp题

dp[i][j]代表第i张彩票价格为j的可能购票个数,那么dp[i][j] = dp[i-1][j-1]+dp[i-1][j-2]+dp[i-1][j-3]+dp[i-1][j-4]

(这么简单的式子居然推了我15分钟T^T,果然还是太菜了)

然后用sum记录 j >= 3*n 的个数,最后用gcd求sum和4^n(总数)的公约数——就完事了

 1 #include
 2 using namespace std;
 3 #define ll long long
 4 #define inf 0x3f3f3f
 5 const int  N = 1e3+10;
 6 ll dp[N][N];
 7 ll gcd(ll a,ll b)
 8 {
 9     if(a<b)
10     swap(a,b);
11     if(a%b)
12     {
13         ll c = a%b;
14         a = b;
15         b = c;
16         return gcd(a,b);
17     }
18     return b;
19      
20 }
21 int main()
22 {
23     int n;
24     while(cin>>n)
25     {
26         if(!n)
27         {
28             cout<<1<<"/"<<1<<endl;
29             continue;
30         }
31         memset(dp,0,sizeof(dp));
32         for(int i=1;i<=4;i++)
33         dp[1][i] = 1;
34         for(int i=2;i<=n;i++)
35         {
36             for(int j=i;j<=4*i;j++)
37             dp[i][j] = dp[i-1][j-1]+dp[i-1][j-2]+dp[i-1][j-3]+dp[i-1][j-4];
38         }
39         ll sum = 0;
40         //dp[i][3*n]
41         for(int j=3*n;j<=4*n;j++)
42             sum+=dp[n][j];
43              
44         ll maxn = pow(4,n);
45         ll gcdd = gcd(maxn,sum);
46         cout<"/"<endl;
47     }
48     return 0;
49 }
View Code

 

 

 

2019-08-22

01:53:55

被3整除的子序列

题目链接:https://ac.nowcoder.com/acm/problem/21302

又是无聊逛牛客看到一道特别眼熟的题,打开一看是曾经AC过了的题

但是点开解题的代码才发现以前是暴力过的...于是就用dp重写了三份

一份是用子序列dp解决,另外两份用的是区间dp

 

dp[i][j]前i个数中合成j的方法数(子区间个数),用这方法要注意0%3也等于0,所以0的作用相当于3(被这个小细节坑了好多时间T^T)

 1 #include
 2 using namespace std;
 3 #define ll long long
 4 #define inf 0x3f3f3f
 5 const ll mod = 1e9+7;
 6 const int N = 1e3+10;
 7 ll dp[N][N];
 8 char s[N];
 9 ll f(char a)
10 {
11     return a-'0';
12 }
13 int main()
14 {
15     ios::sync_with_stdio(false);
16     while(cin>>(s+1))
17     {
18 
19         int ans = 0;
20         int len = strlen(s+1);
21         for(int i=1; i<=len; i++)
22         {
23 
24             for(int j=0; j<1000; j++)
25             {
26                 dp[i][j] = dp[i-1][j];
27                 //如果 s[i] == j  那么前i个数 组成的j的个数+1
28                 if(j == f(s[i]) ) dp[i][j]++;
29                 // s[i]不能单独作为组成j的个数+1,所以寻找 j-s[i]的个数 ,判断能和 s[i]组成j的个数有多少
30                 // 这里还要判断一次等于是因为序列中可能存在0  比如03 和3 为两个不同的序列 ,所以要多个等于的判断(真tm坑!)
31                 if(j >= f(s[i]) ) dp[i][j] = dp[i-1][j - f(s[i]) ] + dp[i][j];
32                 //寻找能和s[i]组成j的个数 , 因为s[i]>j ,所以能和s[i]组成j(s[i]+x=j)的x必为负数,跳过
33 
34                 //if(j
35                 dp[i][j] =dp[i][j] % mod;
36             }
37         }
38         for(int j=0; j<=1000; j++)
39             if(j%3==0)  ans = (ans+dp[len][j])%mod;
40         cout<endl;
41 
42     }
43     return 0;
44 }
子序列dp

 

dp[i][j][k]表示从区间 i 到区间 j  %3等于k的序列的个数

 1 #include
 2 using namespace std;
 3 #define ll long long
 4 #define inf 0x3f3f3f
 5 const ll mod = 1e9+7;
 6 const int N = 1e3+10;
 7 ll dp[N][N][5];
 8 char s[N];
 9 ll f(char a)
10 {
11     return a-'0';
12 }
13 int main()
14 {
15     char s[1000];
16     while(cin>>(s+1))
17     {
18  
19         int len = strlen(s+1);
20         for(int i = 1; i <= len; i++)
21         {
22             dp[i][i][(f(s[i]))%3]=1;
23         }
24         for(int i = 1; i <= len; i++)
25         {
26             for(int j = i+1; j <= len; j++)
27             {
28                 if(f(s[j])% 3 == 0)
29                 {
30                     dp[i][j][0] = 2*dp[i][j-1][0] + 1;
31                     dp[i][j][1] = 2*dp[i][j-1][1];
32                     dp[i][j][2] = 2*dp[i][j-1][2];
33                 }
34                 else if(f(s[j])% 3 == 1)
35                 {
36                     dp[i][j][0] = dp[i][j-1][0] + dp[i][j-1][2];
37                     dp[i][j][1] = dp[i][j-1][1] + dp[i][j-1][0] + 1;
38                     dp[i][j][2] = dp[i][j-1][1] + dp[i][j-1][2];
39                 }
40                 else if(f(s[j])%3 == 2)
41                 {
42                     dp[i][j][0] = dp[i][j-1][0] + dp[i][j-1][1];
43                     dp[i][j][1] = dp[i][j-1][2] + dp[i][j-1][1];
44                     dp[i][j][2] = dp[i][j-1][0] + dp[i][j-1][2] + 1;
45                 }
46                 for(int k = 0; k < 3; k++)
47                     dp[i][j][k] = dp[i][j][k] % mod;
48  
49             }
50         }
51                 cout<1][len][0]<<endl;
52     }
53     return 0;
54 }
区间dp三维

 

化三维为二维的话,dp[i][j]表示前i个数组成的区间中%3等于j的子序列的个数

 1 #include
 2 using namespace std;
 3 #define inf 0x3f3f3f
 4 typedef long long ll;
 5 const ll mod = 1e9+7;
 6 const int N=1e3+10;
 7 typedef long long ll;
 8 char vis[N];
 9 ll dp[N][5];//保存从0到i上,余数为0,1,2的子序列数目
10 ll f(char a)
11 {
12     return a-'0';
13 }
14 int main()
15 {
16     ios::sync_with_stdio(false);
17     string num;
18     while(cin>>num)
19     {
20 
21         int n=num.length();
22         for(int i=0; i)
23         {
24             dp[i][f(num[i])%3]=1;
25         }
26         for(int i=0; i)
27         {
28             if(num[i]%3==0)
29             {
30                 dp[i][0]=2*dp[i-1][0]+1;//自己本身为子序列所以加一
31                 dp[i][1]=2*dp[i-1][1];
32                 dp[i][2]=2*dp[i-1][2];
33             }
34             else if(num[i]%3==1)
35             {
36                 dp[i][0]=dp[i-1][0]+dp[i-1][2];
37                 dp[i][1]=dp[i-1][1]+dp[i-1][0]+1;
38                 dp[i][2]=dp[i-1][2]+dp[i-1][1];
39             }
40             else if(num[i]%3==2)
41             {
42                 dp[i][0]=dp[i-1][0]+dp[i-1][1];
43                 dp[i][1]=dp[i-1][1]+dp[i-1][2];
44                 dp[i][2]=dp[i-1][2]+dp[i-1][0]+1;
45             }
46             for(int j=0; j<3; j++) dp[i][j]%=mod; 
47 
48         }
49         cout<1][0]<<endl;
50     }
51     return 0;
52 }
区间dp二维

 

 

2019-08-22

15:05:21

删括号

题目链接:https://ac.nowcoder.com/acm/problem/21303?&headNav=acm

这道题很明显是道判定性dp题

本来还在琢磨着怎么建立状态方程,突然发现题目给的字符串长度小于等于100,于是果断放弃dp写起了dfs

(然而写条件和调bug一共用了快一个小时的时间,这么想想还不如用dp来得快)

先贴上dfs

 1 #include
 2 using namespace std;
 3 #define ll long long
 4 #define inf 0x3f3f3f
 5 const int N = 1e2 + 10;
 6 string s, t;
 7 int a[N], b[N];
 8 bool dfs(int l1, int r1, int l2, int r2)
 9 {
10     if(l2 >= r2)
11     {
12         return true;
13     }
14     if(l1 >= r1)
15     {
16         return false;
17     }
18     int cnt = 0;
19     int index_a_l = l2;
20     int index_a_r = l2;
21     int index_b_l = l1;
22     int index_b_r = l1;
23     while(index_a_r < r2 && index_b_r < r1)
24     {
25         if(t[index_a_r++] == '(')
26         {
27             cnt++;
28         }
29         else
30         {
31             cnt--;
32         }
33         if(cnt == 0)
34         {
35             int acnt = 0;
36             while(index_b_r < r1)
37             {
38                 if(s[index_b_r++] == '(')
39                 {
40                     acnt++;
41                 }
42                 else
43                 {
44                     acnt--;
45                 }
46                 if(acnt == 0)
47                 {
48                     if(!dfs(index_b_l + 1,index_b_r - 1,index_a_l + 1, index_a_r - 1))
49                     {
50                         index_b_l = index_b_r;
51                         if(index_b_r == r1)
52                         {
53                             return false;
54                         }
55                     }
56                     else
57                     {
58                         index_b_l = index_b_r;
59                         index_a_l = index_a_r;
60                         break;
61                     }
62                 }
63             }
64         }
65     }
66     return index_a_r == r2;
67 }
68 int main()
69 {
70     ios::sync_with_stdio(false); 
71     while(cin>>s>>t)
72     {
73         if(dfs(0,s.length(),0,t.length()))
74         {
75             cout<<"Possible"<<endl;
76         }
77         else
78         {
79             cout<<"Impossible"<<endl;
80         }
81     }
82 }
dfs

下面在贴上dp

dp[i][j][k] 表示 a1...ai和b1...bj在额外多删除k个 ' ( ' 时能否匹配

(代码里写有详细的注释)

 1 #include
 2 using namespace std;
 3 #define ll long long
 4 #define inf 0x3f3f3f
 5 const int  N = 1e2+10;
 6 char a[N],b[N];
 7 bool dp[N][N][N];
 8 int main()
 9 {
10     while(cin>>(a+1))
11     {
12         memset(dp,false,sizeof(dp));
13         cin>>(b+1);
14         dp[0][0][0] = 1;
15         int n = strlen(a+1);
16         int m = strlen(b+1);
17         for(int i=0 ; i)
18         {
19             for(int j = 0; j)
20             {
21                 for(int k = 0; k<=n; k++)
22                 {
23                     if(dp[i][j][k])
24                     {
25                         if(!k && a[i+1] == b[j+1]) dp[i+1][j+1][k] = true;
26                         if(a[i+1]=='(')  dp[i+1][j][k+1] = true;
27                         else if( k == 0 && a[i+1] == ')')   dp[i+1][j][k] = false;
28 //a1...ai 和b1...bj 在额外多删除k个'('时已经匹配
29 //如果k等于0说明此时a1...ai和b1...bj是额外多删除0个'('才匹配,即两者已经完全匹配
30 //那么 此时 dp[i+1][j][k]代表 a1...ai+1和b1...bj还需要额外多删除 0-1 = -1个'('即需要额外添加一个'('才匹配,显然这是不合法的 
31 //如果k大于0说明此时a1...ai和b1...bj是额外多删除k个'('才匹配了
32 //那么由于ai+1为')',所以前面可以额外少删除一个'('使得a1...ai+1和b1...bj匹配 
33                         else if( k > 0 && a[i+1] == ')')  dp[i+1][j][k-1] = true;
34                     
35                                     
36                     }
37                 }
38             }
39         }
40         if(dp[n][m][0]) //a1...an和b1...bm能否额外多删除0个'('使他们匹配 
41         cout<<"Possible"<<endl;
42         else cout<<"Impossible"<<endl;
43     }
44     return 0;
45 }
判断性dp

 

 

2019-08-28

02:52:30

Labyrinth

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4826

今天模拟赛的一道(假)水题

刚开始做的时候看到题目数据范围并不是很大,于是很快就写起dfs,但是提交的时候很遗憾的TLE了,来还以为又是哪里出bug了,就反复改反复提交(好傻呀...)... 于是就一直TLE,直到看了眼别人的提交,大家的运行结果都是TLE,这才发现不太对劲...本来这时候我应该很快反应过来要用dp的,但我却跑去做题去了...最后只留了一点时间来推dp的状态方程,并且发现它要考虑多个方向的状态,好像需要点时间处理,于是就很尴尬的错过了Accept的机会...

赛后看了一下别人的题解,顿时恍然大悟,然后就跟着状态方程摸着摸着把题Acccept了。。。

dp[ i ][ j ][ 0 ]表示从前一个状态往下走到 第 i 行 第 j 列时的最大金币数

dp[ i ][ j ][ 1 ]表示从前一个状态往上走到 第 i 行 第 j 列时的最大金币数

dp[ i ][ j ][ 2 ]表示从前一个状态往右走到 第 i 行 第 j 列时的最大金币数

下面贴代码

 1 #include 
 2 #define inf 0x3f3f3f3f
 3 using namespace std;
 4 const int N = 1e2 + 10;
 5 int mp[N][N];
 6 int dp[N][N][3];
 7 int main()
 8 {
 9     int T;
10     scanf("%d", &T);
11     for (int t = 1; t <= T; ++t)
12     {
13         memset(dp, -inf, sizeof(dp));
14         int n, m;
15         scanf("%d%d", &n, &m);
16         for (int i = 1; i <= n; ++i)
17             for (int j = 1; j <= m; ++j)
18                 scanf("%d", &mp[i][j]);
19         dp[1][1][0] = mp[1][1];
20         dp[1][1][1] = mp[1][1];
21         dp[1][1][2] = mp[1][1];
22 
23         for (int i = 2; i <= n; ++i)
24             dp[i][1][0] = dp[i - 1][1][0] + mp[i][1];
25 
26         for (int j = 2; j <= m; ++j)
27         {
28             //往右走,可以从第一行开始,因为状态的累加是从左边一列累加的
29             //并且三种方向都可以
30 
31             for (int i = 1; i <= n; ++i)
32                 dp[i][j][2] = max(dp[i][j - 1][0], max(dp[i][j - 1][1], dp[i][j - 1][2])) + mp[i][j];
33 
34             //往下走。需要从第二行开始,因为状态的累加是从上一行开始的,第 1 行不可能是由第 0 行向下转移来的 
35             //并且只能够从右边和上面得到
36 
37             for (int i = 2; i <= n; ++i)
38                 dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][2]) + mp[i][j];
39             //往上走,需要从倒数第二行开始,因为状态的累加是从下一行开始的, 第 n 行 不可能是由 第 n+1 行向上转移来的 
40             //并且只能够从右边和下面得到
41 
42             for (int i = n - 1; i >= 1; --i)
43                 dp[i][j][1] = max(dp[i + 1][j][1], dp[i + 1][j][2]) + mp[i][j];
44         }
45         int ans = max(dp[1][m][0], max(dp[1][m][1], dp[1][m][2]));
46         printf("Case #%d:\n", t);
47         printf("%d\n", ans);
48     }
49     return 0;
50 }
View Code

看别人的博客有只用两种状态进行转移的dp,这是因为除了第一列之外的每一行每一列的最大金币数都可

以由前一列向右转移得来,所以我们当 j >= 2 时我们肯定是要考虑到dp[ i ][ j - 1]的状态,而这个状态的表

方式为 dp[ i ][ j - 1 ][ 1 ]或者dp[ i ][ j - 1 ][ 0 ]  (两者的值是一样的),

 1 #include 
 2 using namespace std;
 3 const int N = 105;
 4 const int INF = 0x3f3f3f3f;
 5 int n, m, g[N][N], dp[N][N][2];
 6 
 7 void init()
 8 {
 9     scanf("%d%d", &n, &m);
10     for (int i = 1; i <= n; i++)
11         for (int j = 1; j <= m; j++)
12             scanf("%d", &g[i][j]);
13 }
14 
15 int solve()
16 {
17 
18     for (int i = 1; i <= m; i++)
19         dp[0][i][0] = dp[n + 1][i][1] = -INF;
20 
21     dp[0][1][0] = 0;
22     for (int i = 1; i <= n; i++)
23     {
24         dp[i][1][0] = dp[i - 1][1][0] + g[i][1];
25         dp[i][1][1] = -INF;
26     }
27 
28     for (int j = 2; j <= m; j++)
29     {
30 
31         for (int i = 1; i <= n; i++)
32             dp[i][j][0] = max(dp[i - 1][j][0], max(dp[i][j - 1][0], dp[i][j - 1][1])) + g[i][j];
33 
34         for (int i = n; i >= 1; i--)
35             dp[i][j][1] = max(dp[i + 1][j][1], max(dp[i][j - 1][0], dp[i][j - 1][1])) + g[i][j];
36     }
37     return max(dp[1][m][0], dp[1][m][1]);
38 }
39 
40 int main()
41 {
42     int cas;
43     scanf("%d", &cas);
44     for (int i = 1; i <= cas; i++)
45     {
46         init();
47         printf("Case #%d:\n%d\n", i, solve());
48     }
49     return 0;
50 }
View Code

 

所以代码我们可以简化,如下

 

你可能感兴趣的:(dp专题)