51nod最大M子段和系列(51nod1052,51nod1053&51nod1115)

题目1:51nod1052.
题目大意:给定一个 n n n个数字组成的序列 A A A,在 A A A中选出 M M M个不相交的子段,使得这 M M M个子段的子段和之和最大, 1 ≤ n ≤ 5000 1\leq n \leq 5000 1n5000.

很显然可以想到一个很简单的DP,用 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个数中选 j j j段的且最后一段必须包括第 i i i个数的最优解.然后就可以得到一个最简单的转移方程:
f [ i ] [ j ] = m a x k = 1 i − 1 { f [ k ] [ j − 1 ] + m s [ k + 1 ] [ i ] } f[i][j]=max_{k=1}^{i-1} \{ f[k][j-1]+ms[k+1][i]\} f[i][j]=maxk=1i1{f[k][j1]+ms[k+1][i]}

其中 m s [ i ] [ j ] ms[i][j] ms[i][j]表示从序列 A A A i i i j j j这一段中最大的后缀和(和最大的必须包括 j j j的子段).
这个算法的时间复杂度为 O ( n 3 ) O(n^3) O(n3),并不能通过这道题.

我们试着改进这个方程,发现其实可以这样转移:
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] + A [ i ] , m a x k = 1 i − 1 { f [ k ] [ j − 1 ] } + A [ i ] ) f[i][j]=max(f[i-1][j]+A[i],max_{k=1}^{i-1}\{ f[k][j-1] \}+A[i]) f[i][j]=max(f[i1][j]+A[i],maxk=1i1{f[k][j1]}+A[i])

仔细一想发现 m a x k = 1 i − 1 { f [ k ] [ j − 1 ] } max_{k=1}^{i-1}\{ f[k][j-1] \} maxk=1i1{f[k][j1]}可以在DP的同时通过前缀 m a x max max来做到 O ( 1 ) O(1) O(1)回答,然后就做到了 O ( 1 ) O(1) O(1)转移,综合时间复杂度降为 O ( n 2 ) O(n^2) O(n2).

不过看起来这道题卡空间,用滚动数组不能优化第一层状态,所以要优化第二层,枚举时也先枚举第二层.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=5000;
const LL INF=(1LL<<50)-1LL;
 
LL f[N+9][2],F[N+9][2],a[N+9],n,m,old,now; 
 
Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i)
    scanf("%lld",&a[i]);
}
 
Abigail work(){
  old=1;now=0;
  for (int j=1;j<=m;++j){
    for (int i=1;i<=n;++i)
      f[i][now]=-INF;
    for (int i=1;i<=n;++i)
      f[i][now]=max(f[i-1][now],F[i-1][old])+a[i],F[i][now]=max(f[i][now],F[i-1][now]);
    now^=1;old^=1;
  }
}
 
Abigail outo(){
  printf("%lld\n",F[n][old]);
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}

题目2:51nod1053.
题目大意:题目1的数据加强版, 1 ≤ n ≤ 50000 1\leq n \leq 50000 1n50000.

我们发现DP貌似怎么优化也过不了这题了…

考虑这个问题的性质,发现这个问题中如果没有 m m m的限制的最好情况肯定是将所有正数和0取出.

有了 m m m的限制后,发现取出的 m m m段中任何一段 [ l , r ] [l,r] [l,r]一定满足 A [ l ] A[l] A[l] A [ l − 1 ] A[l-1] A[l1]异号且 A [ r ] A[r] A[r] A [ r + 1 ] A[r+1] A[r+1]异号,于是考虑将原序列 A A A转化为正负交替的形式 B B B,例如序列 1 &ThinSpace; 3 &ThinSpace; − 2 &ThinSpace; 2 &ThinSpace; − 3 &ThinSpace; − 1 &ThinSpace; 0 &ThinSpace; 9 1\,3\,-2\,2\,-3\,-1\,0\,9 13223109就可以处理成 4 &ThinSpace; − 2 &ThinSpace; 2 &ThinSpace; − 4 &ThinSpace; 9 4\,-2\,2\,-4\,9 42249.

那么现在序列 B B B中每一个数都代表了序列 A A A中一段正数或负数的和,且序列 B B B中每一个数B[i]都满足与 B [ i − 1 ] B[i-1] B[i1]异号且与 B [ i + 1 ] B[i+1] B[i+1]异号.

很明显,记 B B B序列中正数的个数为 c n t cnt cnt,当 m ≥ c n t m\geq cnt mcnt时,就是将所有 B B B中正数相加.而 m &lt; c n t m&lt;cnt m<cnt时,我们就需要在 B B B序列中所有正数的基础上,去掉一些正数或加入一些负数,来使得总段数等于 m m m.

也就是说,当 m &lt; c n t m&lt;cnt m<cnt时,我们需要维护序列 C C C初始为 B B B中所有元素取绝对值,即 c [ i ] = ∣ B [ i ] ∣ c[i]=|B[i]| c[i]=B[i].然后我们需要做 c n t − m cnt-m cntm次操作,每一次操作是将 C C C中最小的数 C [ i ] C[i] C[i]删除,并将 C [ i − 1 ] C[i-1] C[i1] C [ i + 1 ] C[i+1] C[i+1]合并.同时记录一个 a n s ans ans初始为 B B B中所有正数之和,然后每次操作时 a n s ans ans减去 C [ i ] C[i] C[i],最后的 a n s ans ans就是答案.

仔细思考一下就会发现上面这个过程十分正确且巧妙.

那么现在我们只需要找到一个数据结构维护 C C C序列即可,很容易想到堆是个不错的选择.但是堆不能完成删除与合并两个相邻元素的操作,于是我们就可以直接用set代替堆进行删除,同时维护一个双向链表来维护合并.时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn).

代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;
#define pair pair<LL,int>
#define mp make_pair<LL,int>
#define fi first
#define se second
 
const int N=50000;
const LL INF=(1LL<<50)-1LL;
 
struct List{
  int next,last;
}q[N+9];
int top,vis[N+9];
set<pair> s;
 
void Erase(int x){
  int l=q[x].last,r=q[x].next;
  if (l) q[l].next=r;
  if (r) q[r].last=l;
}
 
int n,m,cnt,num;
LL a[N+9],b[N+9],ans;
 
void Get_B(){
  b[cnt=1]=a[1];
  for (int i=2;i<=n;++i)
    a[i-1]<0^a[i]<0?b[++cnt]=a[i]:b[cnt]+=a[i];
  for (int i=1;i<=cnt;++i)
    if (b[i]>=0) ans+=b[i],++num;
}
 
void solve(){
  if (num<=m) return;
  for (int i=1;i<=cnt;++i)
    s.insert(mp(abs(b[i]),i));
  for (int i=1;i<=cnt;++i)
    q[i].last=i-1,q[i].next=i+1;
  q[cnt].next=0;
  num-=m;
  pair tmp;int l,r,x;
  while (num){
    tmp=*s.begin();x=tmp.se;
    s.erase(*s.begin());
    l=q[x].last;r=q[x].next;
    if (b[x]<0&&(!l||!r)) continue;      //若当前得到的是一个在边界的负数就不管,这个边界问题比较毒瘤 
    s.erase(mp(abs(b[l]),l));s.erase(mp(abs(b[r]),r));
    ans-=abs(b[x]);
    b[x]+=b[l]+b[r];
    Erase(l);Erase(r);
    s.insert(mp(abs(b[x]),x));
    --num;
  }
}
 
Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i)
    scanf("%lld",&a[i]);
}
 
Abigail work(){
  Get_B();
  solve();
}
 
Abigail outo(){
  printf("%lld\n",ans);
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}

题目3:51nod1115.
题目大意:在题目1的基础上,序列变成了环状,并且 1 ≤ n ≤ 100000 1 \leq n \leq 100000 1n100000.

序列变成了环状怎么办?我们发现双向链表是可以首尾相连变成循环链表来解决环的问题的,而且这个时候就不存在处在边界上的负数这种情况了,其实会比上面的题好写很多.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
#define pair pair<LL,int>
#define mp make_pair<LL,int>
#define fi first
#define se second
 
const int N=100000;
 
struct List{
  int next,last;
}q[N+9];
int top,n,m,cnt,num;
LL a[N+9],b[N+9],ans;
set<pair> s;
 
void Erase(int x){
  q[q[x].next].last=q[x].last;
  q[q[x].last].next=q[x].next;
}
 
void Get_B(){
  b[cnt=1]=a[1];
  for (int i=2;i<=n;++i)
    a[i-1]<0^a[i]<0?b[++cnt]=a[i]:b[cnt]+=a[i];
  if (b[1]<0==b[cnt]<0) b[1]+=b[cnt--];
  for (int i=1;i<=cnt;++i)
    if (b[i]>=0) ++num,ans+=b[i];
}
 
void solve(){
  if (num<=m) return;
  for (int i=1;i<=cnt;++i){
    q[i].last=i-1,q[i].next=i+1;
    s.insert(mp(abs(b[i]),i));
  }
  q[1].last=cnt,q[cnt].next=1;
  num-=m;
  pair tmp;int l,r,x;
  while (num){
    tmp=*s.begin();s.erase(*s.begin());
    x=tmp.se;l=q[x].last;r=q[x].next;
    if (b[x]<=0&&(!l||!r)) continue;
    s.erase(mp(abs(b[l]),l));s.erase(mp(abs(b[r]),r));
    ans-=abs(b[x]);
    b[x]+=b[l]+b[r];
    Erase(l);Erase(r);
    s.insert(mp(abs(b[x]),x));
    --num;
  }
}
 
Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i)
    scanf("%lld",&a[i]);
}
 
Abigail work(){
  Get_B();
  solve();
}
 
Abigail outo(){
  printf("%lld\n",ans);
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}
题目 51nod 3478 涉及一个矩阵问题,要求通过最少的操作次数,使得矩阵中至少有 `RowCount` 行 `ColumnCount` 列是回文的。解决这个问题的关键在于如何高效地枚举所有可能的行列组合,并计算每种组合所需的操作次数。 ### 解法思路 1. **预处理每一行每一列变为回文所需的最少操作次数**: - 对于每一行,计算将其变为回文所需的最少操作次数。这可以通过比较每对对称位置的值是否相同来完成。 - 对于每一列,计算将其变为回文所需的最少操作次数,方法同上。 2. **枚举所有可能的行列组合**: - 由于 `N` `M` 的最大值为 8,因此可以枚举所有可能的行组合列组合。 - 对于每一种组合,计算其所需的最少操作次数,并取最小值。 3. **计算操作次数**: - 对于每一种组合,需要计算哪些行列需要修改,并且注意行列的交叉点可能会重复计算,因此需要去重。 ### 代码实现 以下是一个可能的实现方式,使用了枚举位运算来处理组合问题: ```python def min_operations_to_palindrome(matrix, row_count, col_count): import itertools N = len(matrix) M = len(matrix[0]) # Precompute the cost to make each row a palindrome row_cost = [] for i in range(N): cost = 0 for j in range(M // 2): if matrix[i][j] != matrix[i][M - 1 - j]: cost += 1 row_cost.append(cost) # Precompute the cost to make each column a palindrome col_cost = [] for j in range(M): cost = 0 for i in range(N // 2): if matrix[i][j] != matrix[N - 1 - i][j]: cost += 1 col_cost.append(cost) min_total_cost = float(&#39;inf&#39;) # Enumerate all combinations of rows and columns rows = list(range(N)) cols = list(range(M)) from itertools import combinations for row_comb in combinations(rows, row_count): for col_comb in combinations(cols, col_count): # Calculate the cost for this combination cost = 0 # Add row costs for r in row_comb: cost += row_cost[r] # Add column costs for c in col_comb: cost += col_cost[c] # Subtract the overlapping cells for r in row_comb: for c in col_comb: # Check if this cell is part of the palindrome calculation if r &lt; N // 2 and c &lt; M // 2: if matrix[r][c] != matrix[r][M - 1 - c] and matrix[N - 1 - r][c] != matrix[N - 1 - r][M - 1 - c]: cost -= 1 min_total_cost = min(min_total_cost, cost) return min_total_cost # Example usage matrix = [ [0, 1, 0], [1, 0, 1], [0, 1, 0] ] row_count = 2 col_count = 2 result = min_operations_to_palindrome(matrix, row_count, col_count) print(result) ``` ### 代码说明 - **预处理成本**:首先计算每一行每一列变为回文所需的最少操作次数。 - **枚举组合**:使用 `itertools.combinations` 枚举所有可能的行列组合。 - **计算成本**:对于每一种组合,计算其成本,并考虑行列交叉点的重复计算问题。 ### 复杂度分析 - **时间复杂度**:由于 `N` `M` 的最大值为 8,因此枚举所有组合的时间复杂度为 $ O(N^{RowCount} \times M^{ColCount}) $,这在实际中是可接受的。 - **空间复杂度**:主要是存储预处理的成本,空间复杂度为 $ O(N + M) $。 ### 相关问题 1. 如何优化矩阵中行列的枚举组合以减少计算时间? 2. 在计算行列的交叉点时,如何更高效地处理重复计算的问题? 3. 如果矩阵的大小增加到更大的范围,如何调整算法以保持效率? 4. 如何处理矩阵中行列的回文条件不同时的情况? 5. 如何扩展算法以支持更多的操作类型,例如翻转某个区域的值?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值