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
*/