zoukankan      html  css  js  c++  java
  • DP专题(不定期更新)

    1、UVa 11584 Partitioning by Palindromes(字符串区间dp)

      题意:给出一个字符串,划分为若干字串,保证每个字串都是回文串,同时划分数目最小。

      思路:dp[i]表示以第i位结尾时最小的划分数目(初始均为0)

        状态转移方程1:(初始均为0)[j:i]是回文串(0≤j≤i,0≤i<n:dp[i]=1(j==0);dp[i]=(dp[i]==0?dp[j-1]+1:min(dp[i],dp[j-1]+1))

        状态转移方程2:(初始:dp[i]=i+1)[j:i]是回文串(0≤j≤i,0≤i<n):dp[i]=1(j==0);dp[i]=min(dp[i],dp[j-1]+1))

        状态转移方程3:(初始:dp[i]=i+1(1≤in);其他:dp[i]=0)[j:i]是回文串(1≤j≤i,1≤in):dp[i]=min(dp[i],dp[j-1]+1))

     1 #include<iostream>
     2 #include<memory.h>
     3 #include<string.h>
     4 #include<algorithm>
     5 #include<cstdio>
     6 using namespace std;
     7 const int maxn = 1005;
     8 char s[maxn];
     9 int dp[maxn];
    10 int n;
    11 bool Judge(int st, int ed)
    12 {
    13     for (; st <= ed; st++, ed--)
    14     {
    15         if (s[st] != s[ed]) return false;
    16     }
    17     return true;
    18 }
    19 int main()
    20 {
    21     cin >> n;
    22     while (n--)
    23     {
    24         scanf("%s", s);
    25         memset(dp, 0, sizeof(dp));
    26         int sz = strlen(s);
    27         for (int i = 0; i < sz; i++)
    28         {
    29             for (int j = 0; j <= i; j++)
    30             {
    31                 if (Judge(j, i))
    32                 {
    33                     if (j == 0) dp[i] = 1;
    34                     else dp[i] = dp[i] == 0 ? dp[j - 1] + 1 : min(dp[i], dp[j - 1] + 1);
    35                 }
    36             }
    37         }
    38         cout << dp[sz - 1] << endl;
    39     }
    40     return 0;
    41 }
    View Code

    2、UVA 10534 Wavio Sequence(dp + LIS)

      题意:给出一个字符串,求满足这样条件的子序列的最大长度:长度为奇数(假设为2*k+1),同时前k+1个严格递增,后k+1个严格递减。

      思路:分别从左往右和从右往左分别算出以第i位结尾的最长递增子序列的长度L1、L2,然后得出以第i位为中点的满足条件的子序列的长度:min(L1,L2)*2-1(如果L1、L2包含该位),然后对每一位分别重复上述操作,得出最大长度。

     1 #include<iostream>
     2 #include<vector>
     3 #include<memory.h>
     4 #include<algorithm>
     5 using namespace std;
     6 const int maxn = 10005;
     7 int maxl[maxn];
     8 int maxr[maxn];
     9 int p[maxn];
    10 void exchange(int x, int pos, vector<int>&v, char c)
    11 {
    12     vector<int>::iterator it;
    13     it = lower_bound(v.begin(), v.end(), x);
    14     int p = it - v.begin();
    15     v[p] = x;
    16     if (c == 'l')maxl[pos] = p;
    17     else maxr[pos] = p;
    18 }
    19 int main()
    20 {
    21     int n;
    22     while (cin >> n)
    23     {
    24         vector<int>minlen;
    25         for (int i = 0; i < n; i++) cin >> p[i];
    26         minlen.push_back(p[0]);
    27         maxl[0] = 0;
    28         for (int i = 1; i < n; i++)
    29         {
    30             if (p[i] > minlen.back())
    31             {
    32                 maxl[i] = minlen.size();
    33                 minlen.push_back(p[i]);
    34             }
    35             else
    36             {
    37                 exchange(p[i], i, minlen, 'l');
    38             }
    39         }
    40         minlen.clear();
    41         minlen.push_back(p[n - 1]);
    42         maxr[n - 1] = 0;
    43         for (int i = n - 2; i >= 0; i--)
    44         {
    45             if (p[i] > minlen.back())
    46             {
    47                 maxr[i] = minlen.size();
    48                 minlen.push_back(p[i]);
    49             }
    50             else
    51             {
    52                 exchange(p[i], i, minlen, 'r');
    53             }
    54         }
    55         int len = 0;
    56         for (int i = 0; i < n; i++)
    57         {
    58             int tmp = min(maxl[i], maxr[i]) * 2 + 1;
    59             if (tmp > len) len = tmp;
    60         }
    61         cout << len << endl;
    62     }
    63     return 0;
    64 }
    View Code

    3、uva 11404 - Palindromic Subsequence(dp)

      题意:给出一个字符串,求其最长回文子串,并输出该字串。如果有多个,输出字典序最小的那个。

      思路:如果只求长度:dp[i][j]=dp[i+1][j-1]+2(s[i]==s[j]);dp[i][j]=max(dp[i+1][j],dp[i][j-1])(s[i]!=s[j])

        在此基础上可以用string来保存临时回文子串。

     1 #include<iostream>
     2 #include<string>
     3 #include<memory.h>
     4 using namespace std;
     5 const int maxn = 1005;
     6 struct node
     7 {
     8     int len;
     9     string str;
    10 }dp[maxn][maxn];
    11 
    12 int main()
    13 {
    14     string s;
    15     while (cin >> s)
    16     {
    17         int len = s.length();
    18         memset(dp, 0, sizeof(dp));
    19         for (int i = 0; i < len; i++)
    20         {
    21             dp[i][i].len = 1;
    22             dp[i][i].str = s[i];
    23         }
    24         for (int i = len - 1; i >= 0; i--)
    25         {
    26             for (int j = i; j < len; j++)
    27             {
    28 
    29                 if (s[i] == s[j])
    30                 {
    31                     if (i == j)
    32                     {
    33                         dp[i][j].len = 1;
    34                         dp[i][j].str = s[i];
    35                     }
    36                     else
    37                     {
    38                         dp[i][j].len = dp[i + 1][j - 1].len + 2;
    39                         dp[i][j].str = s[i] + dp[i + 1][j - 1].str + s[j];
    40                     }
    41                 }
    42                 else
    43                 {
    44                     if (dp[i + 1][j].len > dp[i][j - 1].len)
    45                     {
    46                         dp[i][j].len = dp[i + 1][j].len;
    47                         dp[i][j].str = dp[i + 1][j].str;
    48                     }
    49                     else if (dp[i + 1][j].len < dp[i][j - 1].len)
    50                     {
    51                         dp[i][j].len = dp[i][j - 1].len;
    52                         dp[i][j].str = dp[i][j - 1].str;
    53                     }
    54                     else
    55                     {
    56                         dp[i][j].len = dp[i][j - 1].len;
    57                         if (dp[i][j - 1].str < dp[i + 1][j].str) dp[i][j].str = dp[i][j - 1].str;
    58                         else dp[i][j].str = dp[i + 1][j].str;
    59                     }
    60                 }
    61             }
    62         }
    63         cout << dp[0][len - 1].str << endl;
    64     }
    65     return 0;
    66 }
    View Code

    4、UVA 11795 - Mega Man's Mission(状态压缩dp)

      题意:洛克人手里有一把武器,能够杀死部分特定敌人(可以杀死的记为1,无法杀死的记为0),同时,他杀死敌人后,能够使用被杀死的敌人的武器,其武器同样也只能杀死特定的敌人。求能杀死所有敌人的方案数。

      思路:对于n个敌人,共有2^n-1种状态(用一个int即可表示),可以先求出每种状态下能够杀死的敌人atk[i](即该状态i下杀死的敌人的武器被获得后能够杀死的敌人),之后,对于i这种状态,如果该状态下能够杀死敌人j,如果i^(1<<j)这种状态(不能杀死j)下能够杀死j(atk[i^(1<<j)]&1<<j),则dp[i]+=dp[i^(1<<j)]。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<memory.h>
     4 using namespace std;
     5 typedef long long ll;
     6 char s[20];
     7 const int maxn = (1 << 16) + 10;
     8 int N;
     9 int w[maxn];//存储洛克人[0]和敌人[1:]的武器
    10 int atk[maxn];//用来保存每个状态可以杀死的机器人
    11 ll dp[maxn];// 2 的 16 次方会爆 int。 // 用来统计每种状态的顺序方案种数
    12 int Exchange()
    13 {
    14     int ans = 0;
    15     int sz = strlen(s);
    16     for (int i = 0; i < sz; i++)
    17     {
    18         if (s[i] == '1') ans = ans | (1 << i);//第i+1位如果能被杀掉,则第i+1位为1
    19     }
    20     return ans;
    21 }
    22 int main()
    23 {
    24     int T;
    25     cin >> T;
    26     int Case = 1;
    27     while (T--)
    28     {
    29         cin >> N;
    30         for (int i = 0; i <= N; i++)
    31         {
    32             scanf("%s", s);
    33             w[i] = Exchange();
    34             //cout << i << ' ' << w[i] << endl;
    35         }
    36         int total = (1 << N) - 1;//对于N个敌人,一共最多有2^N-1种状态(每个敌人能够杀死或不能杀死)
    37         for (int st = 0; st <= total; st++)
    38         {//st即为二进制存储的状态
    39             atk[st] = w[0];//初始为洛克人所拿的武器能够杀死的人
    40             for (int i = 1; i <= N; i++)//对于每个敌人
    41             {
    42                 int j = i - 1;//i其在所存二进制里的位置
    43                 if (st&(1 << j))
    44                 {//如果该状态可以杀死 i,那么该状态也可以杀死i所能干掉的
    45                     atk[st] = atk[st] | w[i];
    46                 }
    47             }
    48         }
    49         memset(dp, 0, sizeof(dp));
    50         dp[0] = 1;//一个都不杀死的方案数为1
    51         for (int st = 1; st <= total; st++)
    52         {
    53             for (int i = 0; i < N; i++)
    54             {//对于N个敌人
    55                 if (st & 1 << i)
    56                 {//如果该状态能够杀死第i+1个敌人(敌人从1开始编号,但是在二进制存储的杀死状态中存储的位置为i),那么 st 由不能杀死 i 的状态转移过来, 即st^(1<<i)(异或)
    57                     if (atk[st ^ (1 << i)] & 1 << i)//并且st^(1<<i)这种状态能够杀死该敌人
    58                     {
    59                         dp[st] += dp[st ^ (1 << i)];
    60                     }
    61                 }
    62             }
    63         }
    64         printf("Case %d: %lld
    ", Case++, dp[total]);
    65     }
    66     return 0;
    67 }
    View Code

    5、UVA 10564 Paths through the Hourglass(dp)

      题意:有一个漏斗形的图,从某一行只能向左下或右下走,走过的路径上的点数之和要等于S。求路径数,若存在,则输出从第一行最左侧开始,同时所走方向形成的字典序最小的字符串。(由‘L’、‘R’组成)。

      思路:从下往上dp,dp[i][j][k] 代表从(i, j)点往下走到最后一层和为k的方案数,那么,显然可以得到状态转移:

      dp[i][j][k] = dp[i + 1][left][k - val] + dp[i + 1][right][k - val], val = (i, j)格上的数字,left是往坐下走的坐标,right往右下走的坐标

      1 #include<iostream>
      2 #include<memory.h>
      3 #include<cstdio>
      4 using namespace std;
      5 typedef long long ll;
      6 int N, S;
      7 ll dp[50][50][510];
      8 int m[50][50];
      9 void Input()
     10 {
     11     for (int i = 1; i <= N; i++)
     12     {
     13         for (int j = 1; j <= N - i+1; j++)
     14         {
     15             scanf("%d", &m[i][j]);
     16         }
     17     }
     18     for (int i = N+1; i <= 2 * N - 1; i++)
     19     {
     20         for (int j = 1; j <= i - N + 1; j++)
     21         {
     22             scanf("%d", &m[i][j]);
     23         }
     24     }
     25 }
     26 void Print()
     27 {
     28     int pos;
     29     for (int i = 1; i <= N; i++)
     30     {
     31         if (dp[1][i][S])
     32         {
     33             cout <<i-1<< ' ';
     34             pos = i;
     35             break;
     36         }
     37     }
     38     int sum = S;
     39     for (int i = 2; i <= N; i++)
     40     {
     41             if (dp[i][pos - 1][sum - m[i - 1][pos]])
     42             {
     43                 cout << 'L';
     44                 sum -= m[i - 1][pos];
     45                 pos--;
     46             }
     47             else
     48             {
     49                 cout << 'R';
     50                 sum -= m[i - 1][pos];
     51             }
     52     }
     53     for (int j = N+1; j <= 2 * N - 1; j++)
     54     {
     55         if (dp[j][pos][sum - m[j - 1][pos]])
     56         {
     57             cout << 'L';
     58             sum -= m[j - 1][pos];
     59         }
     60         else
     61         {
     62             cout << 'R';
     63             sum -= m[j - 1][pos];
     64             pos++;
     65         }
     66     }
     67     cout << endl;
     68 }
     69 
     70 int main()
     71 {
     72     while (cin >> N >> S, N != 0 || S != 0)
     73     {
     74         Input();
     75         memset(dp, 0, sizeof(dp));
     76         for (int i = 1; i <= N; i++) dp[2 * N - 1][i][m[2 * N - 1][i]] = 1;
     77         for (int i = 2 * N - 2; i >= N; i--)
     78         {
     79             for (int j = 1; j <= i - N + 1; j++)
     80             {
     81                 int v = m[i][j];
     82                 for (int tv = v; tv <= S; tv++)
     83                 {
     84                     dp[i][j][tv] = dp[i + 1][j][tv-v] + dp[i + 1][j + 1][tv-v];
     85                 }
     86             }
     87         }
     88         for (int i = N - 1; i >= 1; i--)
     89         {
     90             for (int j = 1; j <= N - i+1; j++)
     91             {
     92                 int v = m[i][j];
     93                 for (int tv = v; tv <= S; tv++)
     94                 {
     95                     dp[i][j][tv] = dp[i + 1][j][tv-v] + dp[i + 1][j - 1][tv-v];
     96                 }
     97             }
     98         }
     99         ll ans = 0;
    100         for (int i = 1; i <= N; i++) ans += dp[1][i][S];
    101         if (ans == 0) cout << ans << endl << endl;
    102         else
    103         {
    104             printf("%lld
    ", ans);
    105             Print();
    106         }
    107     }
    108     return 0;
    109 }
    View Code

    6、UVA 11552 Fewest Flops

      题意:给出一个长度能够整除k的字符串,整除后得到的子串中的字符看可以重新组合。求最后得到的新的字符串中满足该条件(字串中每个字符都相同)的子串的最小数目。例如:“abccd”中,这样的数目有4个:“a”,"b","cc","d".

      思路:DP[i][j]表示前i段,第i段以第j个字符结尾时最小的满足条件的字串数目。

        DP[i][j] = min(DP[i][j],DP[i - 1][l] + cnt - 1)(i-1段以第l个字符结尾时,该字符和第i段第一个字符相同时)

        DP[i][j] = min(DP[i][j],DP[i - 1][l] + cnt)(i-1段以第l个字符结尾时,该字符和第i段第一个字符不同时)

     1 #include<map>
     2 #include<string>
     3 #include<iostream>
     4 #include<memory.h>
     5 #include<algorithm>
     6 using namespace std;
     7 #define maxn 1020
     8 int dp[maxn][maxn];
     9 int main()
    10 {
    11     int n;
    12     cin >> n;
    13     while (n--)
    14     {
    15         int k;
    16         cin >> k;
    17         string s;
    18         cin >> s;
    19         int l = s.length();
    20         int tl = l / k;
    21         map<char, int>m;
    22         int count = 0;
    23         memset(dp, 0, sizeof(dp));
    24         for (int i = 0; i < k; i++)
    25         {
    26             if (!m[s[i]])
    27             {
    28                 m[s[i]] = 1;
    29                 count++;
    30             }
    31         }
    32         for (int i = 0; i < k; i++) dp[0][i] = count;
    33         for (int j = 1; j<tl; j++)
    34         {
    35             m.clear();
    36             count = 0;
    37             for (int i = 0; i < k; i++)
    38             {
    39                 if (!m[s[i + j*k]])
    40                 {
    41                     m[s[i + j*k]] = 1;
    42                     count++;
    43                 }
    44             }
    45             for (int i = 0; i < k; i++) dp[j][i] = l;
    46             for (int i = 0; i < k; i++)
    47             {
    48                 for (int l = 0; l < k; l++)
    49                 {
    50                     if (m[s[l + (j - 1)*k]] && (count == 1 || s[l + (j - 1)*k] != s[i + j*k]))
    51                     {//如果j-1段中第l个字母在第j段中出现过,并且第j段中字母种类只有一种或者有多种但最后一个字母不是它
    52                         dp[j][i] = min(dp[j][i], dp[j - 1][l] + count - 1);
    53                     }
    54                     else dp[j][i] = min(dp[j][i], dp[j - 1][l] + count);
    55                 }
    56             }
    57         }
    58         int mincount = l;
    59         for (int i = 0; i < k; i++) mincount = min(mincount, dp[tl - 1][i]);
    60         cout << mincount << endl;
    61     }
    62     return 0;
    63 }
    View Code

     7、UVa 11825 Hackers’ Crackdown

      题意:有一个分布式网络,每台电脑都有若干台和它相邻,同时所有电脑都有N种服务运行。现在有个黑客,他想要发送病毒,一台电脑只能植入一种病毒,只有所有电脑都瘫坏才能瘫坏掉一种服务,求其能够干掉的最多的服务种数。

      思路:2进制状态压缩保存每台电脑的邻接状态,并用2进制来保存每种状态下能攻陷的电脑。dp[i]表示i状态下能干掉的最多的服务数目。dp[i] = max(dp[i], dp[i^j] + 1)。详细见注释

     1 //状压DP
     2 #include<iostream>
     3 #include<algorithm>
     4 using namespace std;
     5 const int maxst = (1 << 17);
     6 int ini[20];
     7 int cover[maxst];
     8 int dp[maxst];
     9 int main()
    10 {
    11     int n;
    12     int Case = 1;
    13     while (~scanf("%d", &n))
    14     {
    15         if (n == 0) break;
    16         for (int i = 0; i < n; i++)
    17         {
    18             ini[i] = 0|(1<<i);//包括自身
    19             int m;
    20             scanf("%d", &m);
    21             for (int j = 0; j < m; j++)
    22             {
    23                 int k;
    24                 scanf("%d", &k);
    25                 ini[i] |= (1 << k);//加上邻居
    26             }
    27         }
    28         int total = (1 << n) - 1;//总状态数
    29         for (int i = 0; i <=total; i++)
    30         {
    31             cover[i] = 0;
    32             for (int j = 0; j < n; j++)
    33             {
    34                 if (i&(1 << j))//如果该状态能够瘫坏第j台电脑
    35                 {
    36                     cover[i] |= ini[j];//那么也能瘫坏其邻接的电脑
    37                 }
    38             }
    39         }
    40         dp[0] = 0;
    41         for (int i = 1; i <= total; i++)
    42         {
    43             dp[i] = 0;
    44             //枚举子集
    45             for (int j = i; j > 0; j = (j - 1)&i)
    46             {
    47                 if (cover[j] == total)//如果子集的状态下能够干掉所有的电脑
    48                 {
    49                     dp[i] = max(dp[i], dp[i^j] + 1);//那么该状态的能够关闭的服务为max{该状态下能最多关闭的服务数,该状态i和能够干掉所有的电脑的子集的补集所能能关闭的方案数+1(这个1由该子集j提供)}
    50                 }
    51             }
    52         }
    53         printf("Case %d: %d
    ",Case, dp[total]);
    54         Case++;
    55     }
    56     return 0;
    57 }
    View Code

     8、UVA-10635 Prince and Princess

      题意:求两个序列的公共最长子序列。

      思路:听说最长公共子序列算法会超时……由于序列每个数都不同,可以转换为求b的最长递增子序列,只需将a数组按顺序从1开始重新编号,建立映射。

     1 #include<iostream>
     2 #include<map>
     3 #include<vector>
     4 #include<algorithm>
     5 using namespace std;
     6 const int maxl = 300 * 300;
     7 int numa[maxl];
     8 int numb[maxl];
     9 map<int, int>m;
    10 void exchange(int x, vector<int>&v)
    11 {
    12     vector<int>::iterator it;
    13     it = lower_bound(v.begin(), v.end(), x);
    14     int p = it - v.begin();
    15     v[p] = x;
    16 }
    17 
    18 int main()
    19 {
    20     int t;
    21     int Case = 1;
    22     scanf("%d", &t);
    23     while (t--)
    24     {
    25         int n, p, q;
    26         scanf("%d%d%d", &n, &p, &q);
    27         for (int i = 0; i < p+1; i++)
    28         {
    29             scanf("%d", &numa[i]);
    30             m[numa[i]] = i + 1;
    31         }
    32         for (int i = 0; i < q+1; i++)
    33         {
    34             scanf("%d", &numb[i]);
    35             if (m[numb[i]]) numb[i] = m[numb[i]];
    36             else numb[i] = 0;
    37         }
    38         vector<int>minlen;
    39         minlen.push_back(numb[0]);
    40         for (int i = 1; i < q+1; i++)
    41         {
    42             if (numb[i] > minlen.back()) minlen.push_back(numb[i]);
    43             else
    44             {
    45                 exchange(numb[i], minlen);
    46             }
    47         }
    48         printf("Case %d: %d
    ",Case, minlen.size());
    49         Case++;
    50     }
    51     return 0;
    52 }
    View Code

     9、Uva-10891-Game of Sum

      题意:A和B两个人,每次可以轮流从一个数组的左端或右端开始拿走若干个数字,直到数组为空,A先手。求最优选择下A能比B最多高出多少。

      思路:DP,dp[st][ed] = sum[ed] - sum[st - 1] - min(0,L[st][ed - 1],R[st + 1][ed]),L[st][ed] = min(L[st][ed - 1], dp[st][ed]);R[st][ed] = min(R[st + 1][ed], dp[st][ed]).

     1 #include<cstdio>
     2 #include<string>
     3 #include<cstring>
     4 #include<iostream>
     5 #include<cmath>
     6 #include<algorithm>
     7 using namespace std;
     8 typedef long long ll;
     9 const int INF = 0x3f3f3f3f;
    10 const int maxn = 100;
    11 int n;
    12 //const int maxV=12;
    13 int a[maxn + 10];
    14 int dp[maxn + 5][maxn + 5];
    15 int L[maxn + 5][maxn + 5], R[maxn + 5][maxn + 5];
    16 int sum[maxn + 10];
    17 int main()
    18 {
    19     while (~scanf("%d", &n) && n)
    20     {
    21         for (int i = 1; i <= n; i++)
    22             scanf("%d", &a[i]);
    23         sum[0] = 0;
    24         for (int i = 1; i <= n; i++)
    25         {
    26             dp[i][i] = a[i];//dp[st][ed]表示[st,ed]区间,先手最多得分。
    27             sum[i] = sum[i - 1] + a[i];//L[st,ed]=min{dp[st][k]  } ;(st<=k<=ed)
    28             L[i][i] = R[i][i] = dp[i][i];//R[st,ed]=min{dp[k][ed]};(st<=k<=ed)
    29         }
    30         for (int add = 1; add<n; add++)
    31         {
    32             for (int st = 1; st + add <= n; st++)
    33             {
    34                 int ed = st + add;
    35                 int m = 0;              //m表示此阶段的后手最少能拿多少
    36                 m = min(m, L[st][ed - 1]);//表示从右边拿起
    37                 m = min(m, R[st + 1][ed]);//表示从左边拿起
    38                 dp[st][ed] = sum[ed] - sum[st - 1] - m;
    39                 L[st][ed] = min(L[st][ed - 1], dp[st][ed]);
    40                 R[st][ed] = min(R[st + 1][ed], dp[st][ed]);
    41             }
    42         }
    43         printf("%d
    ", dp[1][n] - (sum[n] - sum[0] - dp[1][n]));
    44     }
    45     return 0;
    46 }
    View Code

    10、Uva 10859 - Placing Lampposts

      题意:在一张图中,选择结点放置路灯,路灯会照亮连接该结点的路径,问使所有路径都能被照到的最小路灯数目为多少?同时在所有最小的方案中,要保证至少被两盏灯照亮的道路最多。

      思路:树形DP。优化:x=M*v1+v2,其中M是比"比v2的最大理论值和v2的最小理论值之差"还要大的数。v1表示放置的路灯数目尽可能小,v2表示被一盏灯照亮的路尽可能小。v1=x/M,v2=x%M,v3=m-v2.(被两盏灯照亮的路)。每放一盏灯 + 2000(m)(因为边的最大数量为1000),每增加1条照亮一次的边 + 1.

      ①节点i处不放街灯,那么i是根或者父亲节点放了街灯。所以dp(i,j)=sum{ dp(v,0) | v取遍i的所有儿子节点 },如果i不是根节点,那么结果+1,因为i和父亲连接的这条边只被一盏灯照亮。

      ②节点i处放街灯,dp(i, j) = sum{ dp(v,1) | v取遍i的所有儿子节点 } +M,如果i不是根节点而且j = 0,那么结果 + 1。

     1 //树形DP
     2 //f(i,j)表示第i盏灯的父亲是否点亮所以j=0|1如果父亲放了,那么自己放或者不放都可以那么f(i,j)=max{∑f(ison,0)∑f(ison,1)},如果父亲没有放置,那么自己必须放那么f(i,0)=∑f(ison,1)但是这个时候要让被灯照亮两次的边尽量多,那么应该让被照亮一次的边尽量的少,那么另m=n×x+yx代表覆盖当前的子树的灯的数量,y代表当前子树中覆盖完成的最少的被照亮一次的边的数量前提是让y的最大值小于n那么这样x就成为了首要重要的权值,y是次要的然后dp方程改一下
     3 
     4 //f(i, 0) = (∑f(ison, 1)) + 1
     5 //加1是因为自己和自己的父亲又有一条边被照亮一次所以加1,
     6 //f(i, 1) = max{ (∑f(ison,0)) + 1,∑f(ison,1) }
     7 #include<iostream>
     8 #include<cstdio>
     9 #include<cstring>
    10 #include<cmath>
    11 #include<algorithm>
    12 #include<string>
    13 #include<vector>
    14 #include<queue>
    15 #include<stack>
    16 #define INF 0x3f3f3f3f
    17 #define NODENUM 1005
    18 #define EDGENUM 1005
    19 #define MAXN 1005
    20 using namespace std;
    21 //优化x=M*v1+v2,其中M是比"比v2的最大理论值和v2的最小理论值之差"还要大的数。v1表示放置的路灯数目尽可能小,v2表示被一盏灯照亮的路尽可能小。v1=x/M,v2=x%M,v3=m-v2.(被两盏灯照亮的路)
    22 int root;
    23 const int m = 2000;//即理论的M
    24 
    25 struct EdgeNode
    26 {
    27     int to, next;
    28 } E[2 * EDGENUM];
    29 int edgenum, head[NODENUM], N, T, M;
    30 bool vis[NODENUM];
    31 int ans;
    32 int dp[NODENUM][2];//[0]表示不放灯,[1]表示放灯
    33 
    34 void init()
    35 {
    36     edgenum = 0;//路条数为0
    37     memset(head, -1, sizeof(head));
    38     memset(vis, 0, sizeof(vis));
    39     ans = 0;
    40 }
    41 
    42 void add(int x, int y)
    43 {
    44     edgenum++;
    45     E[edgenum].next = head[x];
    46     head[x] = edgenum;
    47     E[edgenum].to = y;
    48 }
    49 
    50 void dfs(int s)
    51 {
    52     vis[s] = 1;
    53     int sum0 = 0, sum1 = 0;
    54 
    55     for (int p = head[s]; p != -1; p = E[p].next)
    56     {
    57         int v = E[p].to;
    58         if (!vis[v])
    59         {
    60             dfs(v);
    61             sum0 += dp[v][0];
    62             sum1 += dp[v][1];
    63         }
    64     }
    65     if (s == root) dp[s][0] = min(sum1 + m, sum0), ans += dp[s][0];
    66     else dp[s][1] = min(sum0 + 1, sum1 + m), dp[s][0] = sum1 + m + 1;
    67 }
    68 //每放一盏灯 + 2000(m)(因为边的最大数量为1000),每增加1条照亮一次的边 + 1.
    69 //决策一:节点i处不放街灯,那么i是根或者父亲节点放了街灯。所以dp(i,j)=sum{ dp(v,0) | v取遍i的所有儿子节点 },如果i不是根节点,那么结果+1,因为i和父亲连接的这条边只被一盏灯照亮。
    70 //决策二:节点i处放街灯,dp(i, j) = sum{ dp(v,1) | v取遍i的所有儿子节点 } +M,如果i不是根节点而且j = 0,那么结果 + 1。
    71 void build()
    72 {
    73     scanf("%d%d", &N, &M);
    74     for (int i = 0; i<M; ++i)
    75     {
    76         int a, b;
    77         scanf("%d%d", &a, &b);
    78         add(a, b);//无向边
    79         add(b, a);
    80     }
    81 }
    82 
    83 int main()
    84 {
    85     scanf("%d", &T);//测试用例
    86     while (T--)
    87     {
    88         init();
    89         build();
    90         for (int i = 0; i<N; ++i) if (!vis[i]) dfs(root = i);//可能有多颗树
    91         printf("%d %d %d
    ", ans / m, M - ans%m, ans%m);
    92     }
    93     return 0;
    94 }
    View Code

    11、uva 10817 Headmaster's Headache ( 状态压缩dp+sstream应用)

      题意:学校现在招老师,给出原来任职的老师和前来应聘的老师的信息,求在保证每门科目至少有两个老师教的情况下,最小花费是多少。

      思路:把学科的信息用二进制状态压缩,s1对应还需要一个老师教的科目,s2对应已经足够老师教的科目.状态设为dp[i][s1][s2]:代表到第i个老师为止,校长最少需要花多少钱达到目标。当i < m 时,我们必须选择,状态只能更新为添加老师,当i >= m时, 就可以更新为选或不选老师两种状态。s1:当某个学科还需要1个老师教时,该位记为1,否则记为0;s2:当某个学科已经有足够的老师教时,该位记为1,否则记为0。

     1 #include<iostream>
     2 #include<cstring>
     3 #include<string>
     4 #include<sstream>
     5 #include<algorithm>
     6 using namespace std;
     7 const int INF = 0x7f7f7f7f, MAXN = 131;
     8 int s, m, n, teach[MAXN], cost[MAXN], dp[MAXN][1 << 8][1 << 8];
     9 
    10 int DP(int i, int s1, int s2)//s1对应还需要一个老师教的科目,s2对应已经足够老师教的科目.状态设为dp[i][s1][s2]:代表到第i个老师为止,校长最少需要花多少钱达到目标。当i < m 时,我们必须选择,状态只能更新为添加老师,当i >= m时, 就可以更新为选或不选老师两种状态。
    11 //s1,s2均为2进制数,每一位表示该课程有无老师
    12 //s1:当某个学科还需要1个老师教时,该位记为1,否则记为0
    13 //s2:当某个学科已经有足够的老师教时,该位记为1,否则记为0
    14 {
    15     if (i == m + n) return s2 == (1 << s) - 1 ? 0 : INF;  //每个科目都至少两个老师了,那么就不需要再花钱了
    16     int &ret = dp[i][s1][s2];//引用
    17     if (ret >= 0) return ret;
    18     ret = INF;
    19     if (i >= m) ret = DP(i + 1, s1, s2);         //不选
    20     s2 |= (s1 & teach[i]);                       //老师能教,并且差一个老师,那么一并运算,剩下的就是满足条件的科目
    21     s1 |= teach[i];                              //或上去,没人教的科目肯定变成差一个人教
    22     ret = min(ret, cost[i] + DP(i + 1, s1, s2)); //
    23     return ret;
    24 }
    25 
    26 int main()
    27 {
    28     while (~scanf("%d%d%d",&s,&m,&n))
    29     {
    30         if (s == 0)break;
    31         cin.get();
    32         string ss;
    33         int x;
    34         for (int i = 0; i < m + n; ++i)
    35         {
    36             getline(cin, ss);
    37             stringstream sss(ss);
    38             sss >> cost[i];
    39             teach[i] = 0;
    40             while (sss >> x)
    41             {
    42                 teach[i] |= 1 << (x - 1);
    43             }
    44         }
    45         memset(dp, -1, sizeof dp);
    46         //for (int i = 0; i < m + n; ++i) cout << cost[i] << ':' << teach[i] << endl;
    47         cout << DP(0, 0, 0) << '
    ';
    48     }
    49     return 0;
    50 }
    View Code

     12、hdu 1024 Max Sum Plus Plus

      题意:给n个数,现在需要找到m个区间,使得每个区间内元素的和累加起来最大。

      思路:dp[i][j]表示前j个数在选取第j个数的前提下分成i段的最大值。dp[i][j]=max(dp[i][j-1]+num[j],max(dp[i-1][k]|(0<k<j))+num[j]);dp[i][j-1]+num[j]表示前j-1个分成i组,j放在其他组里;max(dp[i-1][k]|(0<k<j))+num[j]表示前k个分成i-1组的最大值加上第j个独立成组的大小。

     1 #include<iostream>
     2 #include<algorithm>
     3 using namespace std;
     4 int n,m;
     5 const int maxn = 1000010;
     6 const int INF = 0x7fffffff;
     7 
     8 int num[maxn];
     9 int dp_now[maxn];
    10 int max_pre[maxn];
    11 //dp[i][j]表示前j个数在选取第j个数的前提下分成i段的最大值。
    12 //dp[i][j]=max(dp[i][j-1]+num[j],max(dp[i-1][k]|(0<k<j))+num[j]);
    13 //dp[i][j-1]+num[j]表示前j-1个分成i组,j放在其他组里
    14 //max(dp[i-1][k]|(0<k<j))+num[j]表示前k个分成i-1组的最大值加上第j个独立成组的大小
    15 int main()
    16 {
    17     while (~scanf("%d%d", &m, &n))
    18     {
    19         for (int i = 1; i <= n; i++) scanf("%d", &num[i]);
    20         memset(dp_now, 0, sizeof(dp_now));
    21         memset(max_pre, 0, sizeof(max_pre));
    22         int tmax;
    23         for (int i = 1; i <= m; i++)
    24         {
    25             tmax = -INF;
    26             for (int j = i; j <= n; j++)
    27             {
    28                 dp_now[j] = max(dp_now[j - 1]+num[j], max_pre[j - 1] + num[j]);
    29                 max_pre[j - 1] = tmax;
    30                 tmax = max(tmax, dp_now[j]);
    31             }
    32         }
    33         printf("%d
    ", tmax);
    34     }
    35     return 0;
    36 }
    View Code

    13、hdu 1029 Ignatius and the Princess IV

      题意:给出n个数(n为奇数),输出n个数中至少出现(n+1)/2的数。

      思路:扫描一边数组即可。

     1 #include<iostream>
     2 using namespace std;
     3 int main()
     4 {
     5     int n;
     6     while (~scanf("%d", &n))
     7     {
     8         int cal = 0;
     9         int m;
    10         int t;
    11         while (n--)
    12         {
    13             scanf("%d", &t);
    14             if (cal == 0)
    15             {
    16                 m = t;
    17                 cal++;
    18             }
    19             else
    20             {
    21                 if(t != m)
    22                 {
    23                     cal--;
    24                 }
    25                 else cal++;
    26             }
    27         }
    28         printf("%d
    ", m);
    29     }
    30 
    31 
    32 
    33     return 0;
    34 }
    View Code

    14、hdu 1069 Monkey and Banana

      题意:给出若干种类型的的砖块,其底面的长宽可由任意两边确定。现在把这些砖块叠放,砖块上方的砖块的底面的长和宽都必须小于该砖块的长与宽,求最大高度。

      思路:先得到各种砖块各种摆放的方式,然后按底面长和宽从大到小排序。从小的开始dp,如果当前块的长和宽比之前的要大,则累加。

     1 #include<iostream>
     2 #include<algorithm>
     3 using namespace std;
     4 const int maxn = 40;
     5 struct node
     6 {
     7     int l;
     8     int w;
     9     int h;
    10 }blocks[maxn*6];
    11 int dp[maxn * 6];//以i为底的最高高度
    12 bool Cmp(const node&a, const node&b)
    13 {
    14     if (a.l == b.l)return a.w > b.w;
    15     else return a.l > b.l;
    16 }
    17 int main()
    18 {
    19     int n;
    20     int Case = 1;
    21     while (~scanf("%d", &n))
    22     {
    23         if (n == 0)break;
    24         int cnt = 0;
    25         while (n--)
    26         {
    27             int xi, yi, zi;
    28             scanf("%d%d%d", &xi, &yi, &zi);
    29             blocks[cnt].l = xi, blocks[cnt].w = yi, blocks[cnt].h = zi;
    30             cnt++;
    31             blocks[cnt].l = yi, blocks[cnt].w = xi, blocks[cnt].h = zi;
    32             cnt++;
    33             blocks[cnt].l = xi, blocks[cnt].w = zi, blocks[cnt].h = yi;
    34             cnt++;
    35             blocks[cnt].l = zi, blocks[cnt].w = xi, blocks[cnt].h = yi;
    36             cnt++;
    37             blocks[cnt].l = yi, blocks[cnt].w = zi, blocks[cnt].h = xi;
    38             cnt++;
    39             blocks[cnt].l = zi, blocks[cnt].w = yi, blocks[cnt].h = xi;
    40             cnt++;
    41         }
    42         sort(blocks, blocks + cnt, Cmp);
    43         for (int i = 0; i < cnt; i++) dp[i] = blocks[i].h;
    44         for (int i = cnt - 2; i >= 0; --i)
    45         {//从小的开始DP
    46             for (int j = i + 1; j < cnt; j++)
    47             {
    48                 if (blocks[j].l < blocks[i].l&&blocks[j].w < blocks[i].w)
    49                 {
    50                     if (dp[j] + blocks[i].h > dp[i])
    51                     {
    52                         dp[i] = dp[j] + blocks[i].h;
    53                     }
    54                 }
    55             }
    56         }
    57         int ans = 0;
    58         for (int i = 0; i < cnt; i++)
    59         {
    60             ans = max(ans, dp[i]);
    61         }
    62         printf("Case %d: maximum height = %d
    ", Case++, ans);
    63     }
    64 
    65 
    66     return 0;
    67 }
    View Code

    15、hdu 1074 Doing Homework

      题意:每门功课的作业都有自己的截止日期和做完所需时间。同时,每门功课都有自己学分,如果不能在截止日期前上交作业,每拖一天则多扣一个学分。问最少会扣多少学分。

      思路:科目最多有15门,枚举每门的完成状态,从小到大枚举,如果当前状态st下,需要完成某项作业j,则考虑其不需完成该作业的状态stt=st^(1<<j),如果加上这门课后其所扣学分更少,则更新,并记录前驱,以便后续输出。

     1 #include<iostream>
     2 #include<stack>
     3 #include<cstdio>
     4 #include<memory.h>
     5 using namespace std;
     6 int n;
     7 const int maxn = 16;
     8 const int INF = 0x7fffffff;
     9 struct sbj
    10 {
    11     char nm[110];//科目名称
    12     int deadline;//截止日期
    13     int cost;//完成需花费的时间
    14 }homework[maxn];
    15 struct node
    16 {
    17     int tm;
    18     int score;
    19     int pre;
    20     int now;
    21 }dp[1 << maxn];
    22 int main()
    23 {
    24     int t;
    25     scanf("%d", &t);
    26     while (t--)
    27     {
    28         scanf("%d", &n);
    29         {
    30             int total = (1 << n) - 1;
    31             for (int i = 0; i < n; i++)
    32             {
    33                 scanf("%s%d%d", homework[i].nm, &homework[i].deadline, &homework[i].cost);
    34             }
    35             memset(dp, 0, sizeof(dp));
    36             for (int st = 1; st <= total; st++)
    37             {//从小的枚举
    38                 dp[st].score = INF;
    39                 for (int j = n - 1; j >= 0; j--)
    40                 {//从最后往前枚举
    41                     if (st&(1 << j))
    42                     {
    43                         int st2 = st ^ (1 << j);
    44                         int t = dp[st2].tm + homework[j].cost - homework[j].deadline;
    45                         if (t < 0) t = 0;
    46                         if (t + dp[st2].score < dp[st].score)
    47                         {
    48                             dp[st].score = t + dp[st2].score;
    49                             dp[st].pre = st2;
    50                             dp[st].now = j;
    51                             dp[st].tm = dp[st2].tm + homework[j].cost;
    52                         }
    53                     }
    54                 }
    55             }
    56             printf("%d
    ", dp[total].score);
    57             int pre = total;
    58             stack<int>s;
    59             while (pre)
    60             {
    61                 s.push(dp[pre].now);
    62                 pre = dp[pre].pre;
    63             }
    64             while (!s.empty())
    65             {
    66                 printf("%s
    ", homework[s.top()].nm);
    67                 s.pop();
    68             }
    69         }
    70     }
    71     return 0;
    72 }
    View Code

    16、hdu 1087  Super Jumping! Jumping! Jumping!

      题意:现在需要从起点到终点踩格子,每次只能向前踩比当前各自大的数字的格子,求所踩格子的数字之和的最大值。

      思路:dp[i]代表以第i个数结尾的最大上升子序列的和,对于当前的格子i,遍历之前的dp[j](0<=j<=i),如果dp[j]+num[i]>dp[i],则更新dp[i]的值。

     1 #include<iostream>
     2 using namespace std;
     3 const int maxn = 1010;
     4 int num[maxn];
     5 int dp[maxn];//dp[i]代表以第i个数结尾的最大上升子序列的和
     6 int main()
     7 {
     8     int n;
     9     while (~scanf("%d", &n))
    10     {
    11         if (n == 0) break;
    12         for (int i = 0; i < n; i++) scanf("%d", &num[i]);
    13         memset(dp, 0, sizeof(dp));
    14         dp[0] = num[0];
    15         int ans = dp[0];
    16         for (int i = 1; i < n; i++)
    17         {
    18             dp[i] = num[i];
    19             for (int j = 0; j <= i; j++)
    20             {
    21                 if (num[j] < num[i])
    22                 {
    23                     if (dp[j] + num[i] > dp[i])
    24                     {
    25                         dp[i] = dp[j] + num[i];
    26                     }
    27                 }
    28             }
    29             if (dp[i] > ans) ans = dp[i];
    30         }
    31         printf("%d
    ", ans);
    32     }
    33     return 0;
    34 }
    View Code

    17、hdu 1114 Piggy-Bank

      题意:给出空的储钱罐的重量和当前储钱罐的重量,给出n种硬币的重量和面值。求使得储钱罐内的最小的硬币总价值。

      思路:dp[v] = min(dp[v], dp[v - coins[i].w] + coins[i].p);dp[i]表示硬币总重量为i时,储钱罐内硬币的最低价值。

     1 #include<iostream>
     2 #include<algorithm>
     3 using namespace std;
     4 const int maxn = 510;
     5 const int maxw = 10010;
     6 const int maxp = 50010;
     7 const int INF = 0x7fffffff;
     8 
     9 int dp[maxw];
    10 struct coin
    11 {
    12     int p;
    13     int w;
    14 }coins[maxn];
    15 int main()
    16 {
    17     int t;
    18     scanf("%d", &t);
    19     while (t--)
    20     {
    21         int e, f;
    22         scanf("%d%d", &e, &f);
    23         int totalw = f - e;//装硬币的重量
    24         int minwight = maxw;
    25         int n;
    26         scanf("%d", &n);
    27         for (int i = 0; i < n; i++)
    28         {
    29             scanf("%d%d", &coins[i].p, &coins[i].w);
    30             if (coins[i].w < minwight) minwight = coins[i].w;
    31         }
    32         if (minwight > totalw)
    33         {
    34             printf("This is impossible.
    ");
    35             continue;
    36         }
    37         for (int i = 0; i <= totalw; i++) dp[i] = INF;
    38         dp[0] = 0;
    39         for (int i = 0; i < n; i++)
    40         {
    41             for (int v = 0; v <= totalw; v++)
    42             {
    43                 if (v - coins[i].w >= 0&& dp[v - coins[i].w]!=INF)
    44                 {
    45                     /*if(dp[v]>=0)dp[v] = min(dp[v], dp[v - coins[i].w] + coins[i].p);
    46                     else dp[v] = dp[v - coins[i].w] + coins[i].p;*/
    47                     dp[v] = min(dp[v], dp[v - coins[i].w] + coins[i].p);
    48                 }
    49             }
    50         }
    51         if(dp[totalw]<INF)printf("The minimum amount of money in the piggy-bank is %d.
    ", dp[totalw]);
    52         else
    53         {
    54             printf("This is impossible.
    ");
    55         }
    56     }
    57     return 0;
    58 }
    View Code

    18、HDU 1176 免费馅饼

      题意:天上开始掉馅饼,每秒种只有在移动不超过一米的范围内接住坠落的馅饼。刚开始时站在5的位置,问最后最多能接住多少馅饼。

      思路:从最大时间往前DP。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<memory.h>
     4 #include<algorithm>
     5 using namespace std;
     6 int n;
     7 const int maxn = 100010;
     8 const int maxt = 100010;
     9 const int INF = 0x7fffffff;
    10 int dp[maxt][12];
    11 int main()
    12 {
    13     while (~scanf("%d", &n))
    14     {
    15         if (n == 0)break;
    16         memset(dp,0, sizeof(dp));
    17         int maxtt = 0;
    18         for (int i = 0; i < n; i++)
    19         {
    20             int x, t;
    21             scanf("%d%d", &x, &t);
    22             dp[t][x]++;
    23             if (t > maxtt) maxtt=t;
    24         }
    25         for (int i = maxtt-1; i >= 0; i--)
    26         {
    27             for (int st = 0; st <= 10; st++)
    28             {
    29                 if (st == 0) dp[i][st] += max(dp[i + 1][st], dp[i + 1][st + 1]);
    30                 else if(st==10)dp[i][st] += max(dp[i + 1][st], dp[i + 1][st - 1]);
    31                 else dp[i][st] += max(dp[i + 1][st - 1], max(dp[i + 1][st], dp[i + 1][st + 1]));
    32             }
    33         }
    34         printf("%d
    ", dp[0][5]);
    35     }
    36     return 0;
    37 }
    View Code
  • 相关阅读:
    sql练手
    简单工厂模式和抽象工厂模式的区别:面向接口编程
    UML中聚合和组合的关系(笔记)
    如何修改SQL Server 2005服务器名称 (装载)
    .NET 图片处理剪裁
    sql server 中的汉字转化为拼音
    sql server while, case,if..else ... try catch ..对象
    String 和 StringBuilder 的相同点和不同点
    sql 折分字符串并修改数据库表中数据
    SQL Server 与 Excel,Access 数据表的导入导出(注:参照博园.NET大观)
  • 原文地址:https://www.cnblogs.com/ivan-count/p/7211813.html
Copyright © 2011-2022 走看看