题目:
就
【背景描述】
一排 N 个数, 第 i 个数是 Ai , 你要找出 K 个不相邻的数, 使得他们的和最大。
请求出这个最大和。
【输入格式】
第一行两个整数 N 和 K。
接下来一行 N 个整数, 第 i 个整数表示 Ai 。
【输出格式】
一行一个整数表示最大和, 请注意答案可能会超过 int 范围
【样例输入】
3 2
4 5 3
【样例输出】
7
【数据范围】
对于 20% 的数据, N, K ≤ 20 。
对于 40% 的数据, N, K ≤ 1000 。
对于 60% 的数据, N, K ≤ 10000 。
对于 100% 的数据, N, K ≤ 100000 , 1 ≤ Ai ≤ 1000000000。
分析:
40分:
写一个n^2的dp,定义dp[ i ][ j ]为选到第i个,选了j个的最大值。转移很简单,要注意转移顺序。
#include<bits/stdc++.h> using namespace std; #define ri register int #define ll long long #define N 1005 ll read() { ll x=0,fl=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') fl=-1; ch=getchar(); } while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x; } ll dp[N][N][3],n,k,a[N]; int main() { freopen("so.in","r",stdin); freopen("so.out","w",stdout); n=read(); k=read(); for(ri i=1;i<=n;++i) a[i]=read(); if(k>n/2+1) { printf("0 "); return 0; } for(ri j=1;j<=k;++j) for(ri i=2*j-1;i<=n;++i){ dp[i][j][0]=max(dp[i-1][j][1],dp[i-1][j][0]); dp[i][j][1]=dp[i-1][j-1][0]+a[i]; } /* for(ri i=1;i<=n;++i){ for(ri j=1;j<=k;++j) printf("%lld ",dp[i][j][0]); printf(" "); } printf("0:%lld 1:%lld ",dp[n][k][0],dp[n][k][1]);*/ printf("%lld ",max(dp[n][k][0],dp[n][k][1])); } /* 3 2 100000000 300000000 500000000 17 9 4 6 8 1 5 8 2 7 9 7 4 8 5 9 4 1 9 */
100分:
如果不考虑选了一个就不能选两边的限制,直接排序,取前k个。
那如果我们想先选最大的那个,选了后发现其实选这个数两边的更优,想反悔。
怎么实现这种反悔操作呢?
a b c
令我们现在选了b,实际上选a+c更优,那么就可以把a+c-b放入原序列,下次如果发现其更优,就会把a+c-b选了,相当于反悔选b,而选了a+c。
现在对于这种动态插点排序,用优先队列来维护,并用链表记录左右两边的位置是哪一个。
因为有删数操作,所以要用两个数组l和r,实现双向记录。
#include<bits/stdc++.h> using namespace std; #define N 500005 #define ll long long #define ri register int const ll inf=1ll<<62; ll ans=0,a[N]; int n,k,cho[N],l[N],r[N]; struct node{ int id; ll val; }; priority_queue<node> q; bool operator < (const node &a,const node &b)//只能重载 < 小于号 { return a.val<b.val; } int main() { freopen("so.in","r",stdin); freopen("so.out","w",stdout); scanf("%d%d",&n,&k); node tmp; for(ri i=1;i<=n;++i){ scanf("%lld",&a[i]); tmp.id=i; tmp.val=a[i]; l[i]=i-1; r[i]=i+1;//数组模拟链表 q.push(tmp); } //注意边界的初始化 //初始化 a[0] 和 a[n+1]的原因:在a[now]=a[l[now]] + a[r[now]] - a[now];的时候 可能调用到a[0] 和 a[n+1] //然而这两个位置是不存在的 所以应该设成负无穷来防止加到他们重新加入队列中的值。 l[1]=0; r[n]=n+1; a[0]=-inf; a[n+1]=-inf; //-inf一定要负够 否则就会加到a[0] 和 a[n+1] 。 int cnt=0; while(!q.empty()){ while(cho[q.top().id]) q.pop(); node x=q.top(); q.pop(); int now=x.id; if(cnt>=k) break; ans+=x.val; a[now]=a[l[now]] + a[r[now]] - a[now]; cho[l[now]]=1; cho[r[now]]=1; l[now]=l[l[now]]; r[l[now]]=now;//画图理解链表删除的指向 r[now]=r[r[now]]; l[r[now]]=now; cnt++; x.val=a[now],q.push(x); } printf("%lld ",ans); } /* 6 3 100 1 -1 100 1 -1 3 2 4 10 3 17 9 4 6 8 1 5 8 2 7 9 7 4 8 5 9 4 1 9 */
注意:
代码很简单,注意初始化链表的边界!!!
有一道题基本和这道题一样:洛谷P1484 种树
#include<bits/stdc++.h> using namespace std; #define N 500005 #define ll long long #define ri register int ll ans=0,a[N]; int n,k,cho[N],l[N],r[N]; struct node{ int id; ll val; }; priority_queue<node> q; bool operator < (const node &a,const node &b) { return a.val<b.val; } int main() { scanf("%d%d",&n,&k); node tmp; for(ri i=1;i<=n;++i){ scanf("%lld",&a[i]); tmp.id=i; tmp.val=a[i]; l[i]=i-1; r[i]=i+1; q.push(tmp); } l[1]=0; r[n]=n+1; int cnt=0; while(!q.empty()){ while(cho[q.top().id]) q.pop(); node x=q.top(); q.pop(); int now=x.id; if(x.val<0 || cnt>=k) break; ans+=x.val; a[now]=a[l[now]] + a[r[now]] - a[now]; cho[l[now]]=1; cho[r[now]]=1; l[now]=l[l[now]]; r[l[now]]=now; r[now]=r[r[now]]; l[r[now]]=now; cnt++; x.val=a[now],q.push(x); } printf("%lld ",ans); } /* 6 3 100 1 -1 100 1 -1 3 2 4 5 3 17 9 4 6 8 1 5 8 2 7 9 7 4 8 5 9 4 1 9 0 */