[NOI2019]序列
从 \(a_1\dots a_n\) , \(b_1\dots b_n\) 中各选出 \(K\) 个数 , 且至少 \(L\) 组下标在两个数组中都被选择 , 使选出的数总和最大
多组数据 , \(T ≤ 10 , 1 ≤∑n ≤ 10^6 , 1 ≤ L ≤ K ≤ n ≤ 2 × 10^5 , 1 ≤ a_i , b_i ≤ 10^9\)
考场上写的是 \(O(n^4)\) 的 \(DP\) , 设 \(dp[i][j][k][l]\) 为到第 \(i\) 位 , 一共选了 \(j\) 对 , 其中 \(a\) 选了 \(k\) 个 , \(b\) 选了 \(l\) 个的方案数 . 可以过 \(n<=30\) , \(28\)分 . 再往上开内存就不够了 , 直接编译不了 .
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define Debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
typedef long long LL;
const int INF=1e9+7;
inline LL read(){
register LL x=0,f=1;register char c=getchar();
while(c<48||c>57){if(c=='-')f=-1;c=getchar();}
while(c>=48&&c<=57)x=(x<<3)+(x<<1)+(c&15),c=getchar();
return f*x;
}
const int N = 31;
int a[N], b[N], c[N], d[N], e[N];
int n, K, L, T;
/*namespace tanxin{
LL ans = 0;
inline bool cmp1(int x, int y){
return a[x] + b[x] > a[y] + b[y];
}
inline int solve(){
ans = 0;
for(int i = 1; i <= n; ++i) c[i] = i;
sort(c + 1, c + n + 1, cmp1);
for(int i = 1; i <= L; ++i) ans += a[c[i]] + b[c[i]];
for(int i = L + 1; i <= n; ++i) d[i - L] = a[c[i]], e[i - L] = b[c[i]];
sort(d + 1, d + L + 1); sort(e + 1, e + L + 1);
for(int i = 1; i <= K - L; ++i) ans += d[i] + e[i];
printf("%lld\n", ans);
}
};*/
namespace baoli_1{
LL dp[N][N][N][N];
LL ans;
inline void chkmax(LL& a, LL b){ if(b > a) a = b; }
inline void main(){
memset(dp, -1, sizeof dp);
dp[0][0][0][0]=0;
ans = 0;
for(int i = 0; i <= n - 1; ++i)
for(int j = 0; j <= K; ++j)
for(int k = 0; k <= K; ++k)
for(int l = 0; l <= K; ++l)
if(~dp[i][j][k][l]){
chkmax(dp[i+1][j][k][l], dp[i][j][k][l]);
if(j < K && k < K && l < K) chkmax(dp[i+1][j+1][k+1][l+1], dp[i][j][k][l] + a[i+1] + b[i+1]);
if(k < K) chkmax(dp[i+1][j][k+1][l], dp[i][j][k][l] + a[i+1]);
if(l < K) chkmax(dp[i+1][j][k][l+1], dp[i][j][k][l] + b[i+1]);
}
for(int i = L; i <= K; ++i)
chkmax(ans, dp[n][i][K][K]);
printf("%lld\n", ans);
}
};
int main(){
#ifndef file
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
#endif
T = read();
while(T--){
n = read(), K = read(), L = read();
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = 1; i <= n; ++i) b[i] = read();
//tanxin::solve();
if(n <= 150) baoli_1::main();
}
}
然后我又到 \(LOJ\) 上找了一个优秀的做法.
总体思路是先选出 \(K-L\) 组下标不同的 , 再选出 \(L\) 组下标相同的 .
先选出 \(K-L\) 组下标不同的 , 就直接在 \(a\) 和 \(b\) 中选出最大的 \(K-L\) 个 , 再选出剩下 \(L\) 对
如果第一阶段选出了下标相同的 , 就记下选了 \(cnt\) 对 , 第二阶段中有 \(cnt\) 次可以不选相同的 , 这样就能保证至少选了 \(L\) 组相同的 . 此时也就是没有限制 , 直接选出 \(a\) 和 \(b\) 中最大的即可 .
再选出 \(L\) 对 , 有三种选法 : 选出 \(a+b\) 最大的 ; 已经选了 \(a\) 中的 \(b\) 最大的 + \(a\) 最大的 ; 已经选了 \(b\) 中的 \(a\) 最大的 + \(b\) 最大的 . 要用几个堆维护这些下标 , 还要记录每个下标被选的状态 , 并及时更新 . 对于没取满的状态要及时放入该放的堆里 , 对于取满的状态如果是第一阶段要更新 \(cnt\) .
由于要用堆维护"贪心"的下标 , 时间复杂度是 \(O(n\log n)\) , \(n\) 为 \(\sum n = 10^6\) .
本题难点已经加粗 , 具体细节见代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define Debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
typedef long long LL;
const int INF=1e9+7;
inline LL read(){
register LL x=0,f=1;register char c=getchar();
while(c<48||c>57){if(c=='-')f=-1;c=getchar();}
while(c>=48&&c<=57)x=(x<<3)+(x<<1)+(c&15),c=getchar();
return f*x;
}
const int N = 1e6 + 5;
int a[N], b[N], op[N];
bool visa[N], visb[N];
int T, n, K, L, cnt;
struct Rank_a{ inline bool operator()(int& x, int& y){return a[x] < a[y];} };
struct Rank_b{ inline bool operator()(int& x, int& y){return b[x] < b[y];} };
struct Rank_ab{ inline bool operator()(int& x, int& y){return a[x]+b[x] < a[y]+b[y];} };
priority_queue <int, vector<int>, Rank_a> qa,qA;
priority_queue <int, vector<int>, Rank_b> qb,qB;
priority_queue <int, vector<int>, Rank_ab> qC;
inline void update(int x){
if(op[x] == 0) qC.push(x); // 一开始没被选的可以当做一对被选
if(op[x] == 1) qB.push(x);
if(op[x] == 2) qA.push(x);
if(op[x] == 3) cnt++;
}
int main(){
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
T = read();
while(T--){
LL ans = 0; cnt = 0;
while(qa.size()) qa.pop();
while(qb.size()) qb.pop();
while(qA.size()) qA.pop();
while(qB.size()) qB.pop();
while(qC.size()) qC.pop();
n = read(), K = read(), L = read();
for(int i = 1; i <= n; ++i) a[i] = read(), qa.push(i);
for(int i = 1; i <= n; ++i) b[i] = read(), qb.push(i);
for(int i = 1; i <= n; ++i) visa[i] = visb[i] = op[i] = 0;
for(int i = 1; i <= K - L; ++i){
int pa = qa.top(); qa.pop(), visa[pa] = 1, op[pa] |= 1;
int pb = qb.top(); qb.pop(), visb[pb] = 1, op[pb] |= 2;
ans += a[pa] + b[pb];
}
for(int i = 1; i <= n; ++i) update(i);
for(int i = 1; i <= L; ++i){ // 接下来L组都要取相同的
while(visa[qa.top()] == 1) qa.pop();
while(visb[qb.top()] == 1) qb.pop();
while((!qA.empty()) && (op[qA.top()] != 2)) qA.pop();
while((!qB.empty()) && (op[qB.top()] != 1)) qB.pop();
while((!qC.empty()) && (op[qC.top()] != 0)) qC.pop();
if(cnt){ // 如果能保证取到当前的任务,那就不是必须取相同的
int pa = qa.top(); qa.pop(), visa[pa] = 1, op[pa] |= 1;
int pb = qb.top(); qb.pop(), visb[pb] = 1, op[pb] |= 2;
ans += a[pa] + b[pb];
-- cnt;
update(pa); // 一定要及时更新
if(pa^pb) update(pb); // 有可能选了两个下标相同的,最后只更新一次
}
else{ // 取相同的
int type = -1, tmax = 0, tmp;
if((!qA.empty()) && (!qb.empty()) && ((tmp = a[qA.top()] + b[qb.top()]) > tmax)) type = 1, tmax = tmp;
if((!qB.empty()) && (!qa.empty()) && ((tmp = a[qa.top()] + b[qB.top()]) > tmax)) type = 2, tmax = tmp;
if((!qC.empty()) && ((tmp = a[qC.top()] + b[qC.top()]) > tmax)) type = 3, tmax = tmp;
ans += tmax; ///
if(type == 1){
int pa = qA.top(); qA.pop(), visa[pa] = 1, op[pa] |= 1;
int pb = qb.top(); qb.pop(), visb[pb] = 1, op[pb] |= 2, update(pb);
}
if(type == 2){
int pa = qa.top(); qa.pop(), visa[pa] = 1, op[pa] |= 1, update(pa);
int pb = qB.top(); qB.pop(), visb[pb] = 1, op[pb] |= 2;
}
if(type == 3){
int pc = qC.top(); qC.pop(), visa[pc] = visb[pc] = 1, op[pc] = 3;
// 就是要取这一对,不用再update更新cnt
}
}
}
printf("%lld\n", ans);
}
}
[NOI2019]序列的更多相关文章
- 【题解】Luogu P5470 [NOI2019]序列
原题传送门 同步赛上我一开始想了个看似正确却漏洞百出的贪心:按\(a_i+b_i\)的和从大向小贪心 随便想想发现是假的,然后就写了个28pts的暴力dp 杜神后半程说这题就是个贪心,但我没时间写了 ...
- [NOI2019]序列(模拟费用流)
题意: 有两个长度为n的序列,要求从每个序列中选k个,并且满足至少有l个位置都被选,问总和最大是多少. \(1\leq l\leq k\leq n\leq 2*10^5\). 首先,记录当前考虑到的位 ...
- luogu P5470 [NOI2019]序列 dp 贪心 费用流 模拟费用流
LINK:序列 考虑前20分 容易想到爆搜. 考虑dp 容易设\(f_{i,j,k,l}\)表示前i个位置 选了j对 且此时A选择了k个 B选择了l个的最大值.期望得分28. code //#incl ...
- 洛谷 P5470 - [NOI2019] 序列(反悔贪心)
洛谷题面传送门 好几天没写题解了,写篇题解意思一下(大雾 考虑反悔贪心,首先我们考虑取出 \(a,b\) 序列中最大的 \(k\) 个数,但这样并不一定满足交集 \(\ge L\) 的限制,因此我们需 ...
- Luogu P5470 [NOI2019]序列
题目 可以直接贪心,但是用模拟费用流推的话会更轻松. 首先有一个显然的建图方式: \(S\)到\(0\)流量为\(k\),费用为\(0\). \(0\)到\(a_i\)流量为\(1\),费用为\(-a ...
- 退役II次后做题记录
退役II次后做题记录 感觉没啥好更的,咕. atcoder1219 历史研究 回滚莫队. [六省联考2017]组合数问题 我是傻逼 按照组合意义等价于\(nk\)个物品,选的物品\(\mod k\) ...
- 模拟费用流 & 可撤销贪心
1. CF730I Olympiad in Programming and Sports 大意: $n$个人, 第$i$个人编程能力$a_i$, 运动能力$b_i$, 要选出$p$个组成编程队, $s ...
- LOJ 3158: 「NOI2019」序列
题目传送门:LOJ #3158. 题意简述: 给定两个长度为 \(n\) 的正整数序列 \(a,b\),要求在每个序列中都选中 \(K\) 个下标,并且要保证同时在两个序列中都被选中的下标至少有 \( ...
- 【NOI2019集训题2】 序列 后缀树+splay+dfs序
题目大意:给你一个长度为$n$的序列$a_i$,还有一个数字$m$,有$q$次询问 每次给出一个$d$和$k$,问你对所有的$a_i$都在模$m$意义下加了$d$后,第$k$小的后缀的起点编号. 数据 ...
随机推荐
- oracle自连接
自连接:通过表的别名,将同一张表视为多张表 select e.ename 员工姓名,b.ename 老板姓名 from emp e,emp b where e.mgr=b.empno; 注:自连接不适 ...
- 第三章、HTTP报文
1 报文流 HTTP 报文是在 HTTP 应用程序之间发送的数据块.这些数据块以一些文本形式的元信息(meta-information)开头.这些报文在客户端.服务器和代理之间流动.术语“流入”.“流 ...
- JavaWeb_(SpringMVC框架)测试SpringMVC&Spring&MyBatis三大整合
搭建 SpringMVC&Spring&MyBatis三大整合 传送门 1.准备 测试搭建S pringMVC&Spring&MyBatis三大整合 用例 a)准备 ...
- fluent中隐藏模型的开启【转载】
转载自:http://blog.sina.com.cn/s/blog_5fd791530100d5ic.html fluent中设置了一些隐藏模型,普通的用户界面是没有相关选项的,必须用相关命令开启. ...
- csp-s模拟90
T1: 每格的不透明度相当与一个边权,转化为从起点到终点所有路径的最大值.实现最长路,最好用$dijk$. T2: 对于$N=100$,$M=8$,考虑状压$dp$.要用一种状态表示某一行的矩形覆盖情 ...
- 安装APK时报错:Failure [INSTALL_FAILED_TEST_ONLY: installPackageLI]
安装APK时报错:Failure [INSTALL_FAILED_TEST_ONLY: installPackageLI] 可以使用adb install -t 解决 对于已经在手机的文件可以使用pm ...
- React的Virtual DOM厉害了
React 的伟大之处就在于,提出了Virtual DOM这种新颖的思路,并且这种思路衍生出了React Native,有可能会统一Web/Native开发. 在性能方面,由于用到了Virtual D ...
- 性能分析 | 线上CPU100%排查
不知道在大家面试中,有没有遇到这个问题: 生产服务器上部署了几个java程序,突然出现了CPU100%的异常告警,你如何定位出问题呢? 这个问题分为两版回答! 高调版 对不起,我是做研发的,这个问题在 ...
- [go]grpc远程接口调用实现
// grpc序列化/反序列化成对应语言的对象 // 1.写idl(数据类型+方法) // 2.生成对应语言的序列化/反序列化代码 // 3.方法需要自己实现 // 环境(将gopath/bin加入p ...
- 手把手教你实现RecyclerView的下拉刷新和上拉加载更多
手把手教你实现RecyclerView的下拉刷新和上拉加载更多 版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https:// ...