CF1038E Maximum Matching 搜索/区间DP

题目传送门:http://codeforces.com/problemset/problem/1038/E

题意:给出$N$个方块,每个方块有左右两种颜色$a,b$(可以翻转使左右两种颜色交换)和一个权值$w$。当某个方块的右侧颜色与另一个方块的左侧颜色相同,它们可以连成一个大块,一个大块可以由若干个小块像这样连成(一个大块中可以只包含一个小块)。定义大块的权值为组成它的所有小块的权值和,问可以连成的大块中最大的权值。$N \leq 100 , a , b \leq 4 , w \leq 10^5$


看到$a,b \leq 4$就是神题预定

方法一:搜索

将四种颜色看做点,方块是连接左右两种颜色的边,就可以得到一个边为$100$,点为$4$的图,目标是找到一个边权和最大的简单路。然后我们可以发现图上很多的边是没有意义的。比如:

①某个方块左右两侧同色(在图上表示自环),这样子的边权直接算作点权即可。

②很多方块左右颜色相同(在图上表示重边),因为这些过多的重边可以通过在两个端点不断绕环,所以在搜索的时候经过它们很多次是没有必要的,所以考虑根据奇偶性将边数大于$3$的重边压成边数为$3$或者$2$(考试的时候想的是$2$或$1$但是考虑边为奇数的也有可能最后丢掉一条边到另外的颜色,所以必须留下$2$或$3$条),其中$2$或$1$条为原来最短边和次短边/最短边,而剩下的一条边则为其他重边压成的边,边权为它们的和。

经过这两种处理,图上的边的数量变为了最多$18$条,搜索可以承受(而且跑得贼快)。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 
 4 vector < int > Edge[5][5];
 5 int pri[5] , cnt[5][5] , N , ans;
 6 
 7 bool cmp(int a , int b){
 8     return a > b;
 9 }
10 
11 void dfs(int now , int cnt){
12     int k = pri[now];
13     cnt += pri[now];
14     pri[now] = 0;
15     for(int i = 1 ; i <= 4 ; i++)
16         if(!Edge[now][i].empty()){
17             int t = Edge[now][i][0];
18             Edge[now][i].erase(Edge[now][i].begin());
19             Edge[i][now].erase(Edge[i][now].begin());
20             dfs(i , cnt + t);
21             Edge[now][i].insert(Edge[now][i].begin() , t);
22             Edge[i][now].insert(Edge[i][now].begin() , t);
23         }
24     ans = max(ans , cnt);
25     pri[now] += k;
26 }
27 
28 int main(){
29     cin >> N;
30     for(int i = 1 ; i <= N ; i++){
31         int a , b , c;
32         cin >> a >> b >> c;
33         swap(b , c);
34         if(a == b)
35             pri[a] += c;
36         else{
37             Edge[a][b].push_back(c);
38             Edge[b][a].push_back(c);
39             cnt[a][b]++;
40             cnt[b][a]++;
41         }
42     }
43     for(int i = 1 ; i <= 4 ; i++)
44         for(int j = 1 ; j <= 4 ; j++){
45             sort(Edge[i][j].begin() , Edge[i][j].end() , cmp);
46             if(cnt[i][j] > 3)
47                 if(cnt[i][j] & 1){
48                     int sum = 0 , p = Edge[i][j][cnt[i][j] - 2] , q = Edge[i][j][cnt[i][j] - 1];
49                     for(int k = 0 ; k < cnt[i][j] - 2 ; k++)
50                         sum += Edge[i][j][k];
51                     Edge[i][j].clear();
52                     Edge[i][j].push_back(sum);
53                     Edge[i][j].push_back(p);
54                     Edge[i][j].push_back(q);
55                 }
56                 else{
57                     int sum = 0 , p = Edge[i][j][cnt[i][j] -1];
58                     for(int k = 0 ; k < cnt[i][j] - 1 ; k++)
59                         sum += Edge[i][j][k];
60                     Edge[i][j].clear();
61                     Edge[i][j].push_back(sum);
62                     Edge[i][j].push_back(p);
63                 }
64         }
65     for(int i = 1 ; i <= 4 ; i++)
66         dfs(i , 0);
67     cout << ans;
68     return 0;
69 }

方法二:DP

设$f_{i,j,k,l}$表示选择$i$到$j$段的方块,大块左端颜色为$k$,右端颜色为$r$的时候获得的最大权值,考虑转移:

①由其中两段拼接而成:$f_{i,p,k,q}+f_{p+1,j,q,l}$

②由其中两段换位拼接而成(因为本身方块的拼接是无序的):$f_{i,p,q,l}+f_{p+1,j,k,q}$

③只取左边一部分或者右边一部分:$f_{i,p,k,l}$或$f_{p,j,k,l}$

注意非法转移情况要用极小值覆盖,所以才有③转移才会产生最优值的情况。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 
 4 int dp[101][101][5][5] , l[101] , r[101] , w[101];
 5 
 6 int main(){
 7     int N;
 8     cin >> N;
 9     for(int i = 1 ; i <= N ; i++)
10         cin >> l[i] >> w[i] >> r[i];
11     memset(dp , -0x3f , sizeof(dp));
12     for(int i = 1 ; i <= N ; i++)
13         dp[i][i][l[i]][r[i]] = dp[i][i][r[i]][l[i]] = w[i];
14     for(int i = N - 1 ; i ; i--)
15         for(int j = i + 1 ; j <= N ; j++)
16             for(int p = 1 ; p <= 4 ; p++)
17                 for(int q = 1 ; q <= 4 ; q++)
18                     for(int k = i ; k < j ; k++){
19                         dp[i][j][p][q] = max(dp[i][j][p][q] , max(dp[i][k][p][q] , dp[k + 1][j][p][q]));
20                         for(int l = 1 ; l <= 4 ; l++)
21                             dp[i][j][p][q] = max(dp[i][j][p][q] , max(dp[i][k][p][l] + dp[k + 1][j][l][q] , dp[i][k][l][q] + dp[k + 1][j][p][l]));
22                     }
23     int all = 0;
24     for(int i = 1 ; i <= 4 ; i++)
25         for(int j = 1 ; j <= 4 ; j++)
26             all = max(all , dp[1][N][i][j]);
27     cout << all;
28     return 0;
29 }

 

转载于:https://www.cnblogs.com/Itst/p/9774902.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值