递推计算贡献(gym/102028/problem/I)
水平线上有n个点,从左到右标记为1到n。第i点到第(i+1)点的距离是ai对于每个从1到n的整数k,要求您在直线上精确地选择k个不同的给定点,以最大化所选点对之间距离的总和。
输入
第一行包含一个正整数T,表示测试用例的数量,T <= 1000,每个测试用例第一行包含一个整数n,表示点的数量,其中2 <n< 10^5第二行包含(n-1)个正整数a1、a2...an-1,其中1 <= ai <= 10^4,我们保证所有测试样例n的总和不会超过10^6。
输出
包含n个整数的一行行,第i个整数是 k = i 时距离的最大和。您应该在每两个相邻的数字之间精确地输出一个空格,并避免这一行中的任何尾随空格。
解析:
题目中要求在给出n个点的n-1个距离间隔上,找出任意取k(1<=k<=n)个点的最大的点与点之间的距离总和是多少。我们可以通过简单分析得到在选点的时候我们应该选取当前可增加最大距离的点,于是我们可以在纸上手动模拟选点,经过模拟后我们可以发现,每次选点的时候我们都是选取的离上一个点的最大距离的点,因为这样才能让距离总和更大,于是我们选点就变成了选一个最左端的、选一个最右端的(也可对调顺序,结果一致)这样一直选下去。
竟然我们知道了要如何选点,那我们接下来就要分析如何来计算距离和。我们在模拟选点的时候应该会发现一个规律,假如我们此时选取了两个左边的点和两个右边的点,那么我们下一次选取的点所增加的距离和上一次是一样的(这就像固定左右端点,你在其中任何一个地方选点到左右端点的和都是左右端点的差值,在纸上画一画就知道了),而在你选取下一个点时(此时是左边选了3个,右边选了2个),我们可以发现 距离增加=上一次增加的+此点和左边离他最近的点的距离(左边离的最近的即为上一次新增的点)。
有了以上结论,我们就可以在O(n)的复杂度下求出k = (1 ~ n)的最距离和了。
程序:
#include<bits/stdc++.h> #define SISWS std::ios::sync_with_stdio(false) #define INF 0x3f3f3f3f using namespace std; template<class T>inline void read(T &res) { char c;T flag=1; while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0'; while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag; } typedef long long ll; const int maxn = 1e5 + 10; int n; int a[maxn];//储存n个点所在的位置 ll ans[maxn];//储存答案 void solve(){ cin>>n; a[1] = 0; for(int i = 2,ai;i <= n;i ++){ cin>>ai; a[i] = a[i - 1] + ai;//计算第i个点在的位置 } ll temp = 0;//储存加一个点会增加多少距离 int l = 1,r = n; for(int i = 1;i <= n;i ++){ if(i%2){//选取左边的点 ans[i] = ans[i - 1] + temp;//答案为上一次的距离 + 增加的距离 }else{ temp += a[r] - a[l]; // 解析中有解释 ans[i] = ans[i - 1] + temp; //答案为上一次的距离 + 增加的距离 l ++,r --; } } for(int i = 1;i < n;i ++){ cout<<ans[i]<<" "; } cout<<ans[n]<<endl; } int main(){ SISWS; int T; cin>>T; while(T--){ solve(); } return 0; }