cf ec 模拟 区间dp

C.Berland Regional
首先因为每个学校需要对应每个学校,所以用vector 存每个学校的学生,又因为我们要求 从大到小的前某些数的和,所以想到用一个vector sum 存前缀和。
对于求ans的优化。
如果暴力 n 个情况会超时。
这时候我们选择开一个ans数组,通过遍历每个学校,将每个学校可以分成的组加到对应的ans中。

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 9;
int n, m, k;
vector <long long> sum[N];
vector <int> v[N];
long long  ans[N];
struct st
{
	long long  u, s;
}a[N];
int main()
{
	int t;
	cin >> t;
	while(t--)
	{
		cin >> n;
		for(int i = 1; i <= n; ++i) {
			v[i].clear(); sum[i].clear(); ans[i] = 0;
		}
		for(int i = 1; i <= n; ++i)
		{
			scanf("%d", &a[i].u);
		}
		for(int i = 1; i <= n; ++i)
			scanf("%d", &a[i].s);
		for(int i = 1; i <= n; ++i) v[a[i].u].push_back(a[i].s);
		for(int i = 1; i <= n; ++i)
		{
			if(v[i].empty()) continue;
			sort(v[i].begin(), v[i].end());
			reverse(v[i].begin(), v[i].end());// 从大到小排序 
			for(int j = 0; j < v[i].size(); ++j)
			{
				sum[i].push_back( (sum[i].empty() ? 0 : sum[i][j-1]) + v[i][j]);
			}
			for(int j = 1; j <= v[i].size(); ++j)
			{
				ans[j] += sum[i][v[i].size() / j * j - 1];
							// sum[i]的前缀和是从0开始的  所以要减1 
			}
		}
		for(int i = 1; i <= n; ++i)
		{
			if(i > 1) cout << " ";cout << ans[i];
		}
		cout << endl;
	}
	return 0;
}

我的思路 + 求ans改进后的ac代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 9;
int n, m, k;
long long ans[N];
struct st
{
	int u;
	long long s;
}a[N];
bool cmp(st a, st b)
{
	if(a.u != b.u) return a.u < b.u;
	else return a.s > b.s;
}
int main()
{
	int t;
	cin >> t;		
	while(t--)
	{
		cin >> n;
		set <int> ss;
		for(int i = 1; i <= n; ++i)
		{
			scanf("%d", &a[i].u);
			ss.insert(a[i].u);
			ans[i] = 0;
		}
		for(int i = 1; i <= n; ++i)
			scanf("%lld", &a[i].s);
		sort(a+1,a+1+n,cmp);
		int j = 1;
		vector <int> v;// 存每个学校的最后一个学生的下标 
		for(int _ = 1; _ <= ss.size(); ++_)
		{
			while(a[j].u == a[j+1].u && j < n) ++j;
			v.push_back(j);
			++j;
			if(j > n) break;
		}
		// 把同校的学生放一起  分成一块一块的 
		a[0].s = a[0].u = 0;
		for(int i = 1; i <= n; ++i)
		{
			if(i == 1) continue;
			if(a[i].u == a[i-1].u) a[i].s += a[i-1].s;
		}
		// 求出每一块的前缀和   仅是同校的才存前缀和  存所有的会溢出,太大了。。。 
		for(int j = 0; j < v.size(); ++j) // 分为 k 个学校 
		{
			if(!j) // 第一个学校 
			{
				for(int k = 1; k <= v[j]; ++k)
					ans[k] += a[v[j] / k * k].s;
			}
			else 
			{
				int l = v[j] - v[j-1];
				for(int k = 1; k <= l; ++k)
				{
					// l / k * k 就是一共能派出实力靠前的总的学生数 
					if(l / k * k != 0) ans[k] += a[v[j-1] + l / k * k].s;
					// v[j-1] 是上一个学校的下标,我们要求当前学校的前缀和,就需要加上这个下标 再 加上 一共派出的学生数 
					// 如果 l / k * k = 0 的话,说明这个学校派不出学生, 不用if判断的话会加上 上一个学校的总前缀和
				}
			}
		}
		for(int i = 1; i <= n; ++i)
		{
			if(i > 1) cout << " ";cout << ans[i];
		}
		cout << endl;
	}
	return 0;
}
/*
1
6
1 1 1 2 2 2
1
6
1 1 2 2 3 3
1 1 1 1 1 1
1
6
1 2 3 4 5 6
1 1 1 1 1 1
1
6
3 3 3 3 3 3
1 1 1 1 1 1
*/

D Maximum Sum of Products
至多反转一次序列 a
这个代码思路感觉非常巧妙

c[i + j] 存的是,最小的 i 到 最大的 j 之间,交换这段序列 增加多少 或 减少多少。
利用的是一段子序列 对称的两个下标加和相等

仔细想想好像就是反转每一段求MAX了
感觉最后扫一遍数组c求max比较保险。

#include<iostream> 
#include<algorithm>
using namespace std;
const int N=5e3+10;
int n;
int a[N],b[N];
int c[N*2];
signed main()
{
	cin>>n;
	int mx=0;
	for(int i=1;i<=n;i++)	cin>>a[i];
	for(int i=1;i<=n;i++)
	{
		cin>>b[i];
		mx+=a[i]*b[i];
	}
	int x = mx;
	for(int i = 2;i <= n; i++)
	{
		for(int j = 1;j < i; j++)
		{
			c[i + j] += (a[i] - a[j]) * b[j] + (a[j] - a[i]) * b[i];
						// 把这个展开就好理解
						// 其实就是交换前的成绩加和 - 交换后的成绩加和
						//  计算交换两个数的前后差值 
			//cout << i + j << " " << c[i+j] << endl;
			mx = max(mx, x + c[i + j]);
		}
	}
	cout<<mx<<endl;
	return 0;
}

/*
5
2 3 2 1 3
1 3 2 4 2
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值