Task.1 完全背包
题目大意:物品 \(n\) 件,背包体积 \(m\),物品体积 \(a_i\) 价值 \(b_i\),每件物品无数件。求不超过容量上限的最大价值。
数据范围:\(1\leq N\leq 10^6,1\leq M\leq 10^{16},a_i,b_i\leq 100\)
题如其名,真的是完全背包。观察数据范围后发现对于 \(a_i,b_i\),\(a_i\) 相同的明显可以舍弃 \(b_i\) 小的,物品数量下降至 \(100\)。比较容易想到在一定范围内,贪心地选取单位体积价值大的物品,小范围内做背包。至于这个范围的大小题解给做出了相关讨论:
引理:给定任意 \(n\) 个整数,它们之中存在若干个整数的和为 \(n\) 的倍数。
定理:假设贪心的选取若干件性价比最高的物品,此外选择 \(x\) 件其他的物品是最优的情况,那么存在一种最优情况使得 \(x\leq val_{max}\) 。
至于证明?想想为什么。
此外还存在一种矩阵优化普通的 \(dp\) 的做法。
通常的矩阵加速递推是形如 \(f_i=\sum^{i-1}_{j=i-k} c_{i-j}\cdot f_j\) 的形式,用普通的矩乘就能优化。对于 \(\min/\max\) 一类的操作,他们和加法具有类似的性质。如果定义一种矩阵的取 \(\max\) 运算(\(A\ \max\ B=C,c_{i,j}=max\{max(a_{i,k},b_{k,j})\}\)),这种运算满足结合律,也可以用矩乘快速幂优化。
代码请转至:https://www.cnblogs.com/15owzLy1-yiylcy/protected/p/11379610.html
鸣谢 \(15owzly1\)。
Task.2 中间值
题目大意:有两个长度均为 \(n\) 的单增不降序列 \(a,b\),给出 \(m\) 次修改和查询操作。每次修改操作给出参数 \(s,x,y\) ,表示修改 \(a\ [s=0]\) 或 \(b\ [s=1]\) 中的位置 \(x\) 上的值为 \(y\),保证修改操作不破坏单增不降的性质。查询操作给出区间 \([l_1,r_1],[l_2,r_2]\),询问两段区间合并后的 第 \(k\) 大 中位数,保证查询操作两区间长度和为奇数。
数据范围:\(1\leq N\leq 5\cdot 10^5,1\leq M\leq 10^6,0\leq a_i,b_i,y\leq 10^9,1\leq l_1\leq r_1\leq N,1\leq l_2\leq r_2\leq N\)
修改操作可以 \(O(1)\)。
可以想到一个二分的做法:二分一个数字作为中位数,每次在两段区间中二分这个数的位置,判断答案是否合法。单次查询复杂度 \(O(\log^2 N)\)。期望得分 \(70\) (还要卡一卡,所以我没写)。
考虑换个二分的对象,二分答案在一个序列中的位置,再根据位置去另一个序列的期望位置判断合不合法,这样单次就只有 \(O(\log N)\) 。
代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
template<class T>void read(T &x){
x=0; char c=getchar();
while(c<'0'||'9'<c)c=getchar();
while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}
const int N=500050;
int n,m;
int a[N],b[N];
int inf=1e9+1,fni=-1e9-1;
int solve(int La,int Ra,int Lb,int Rb){
int l=La,r=Ra,mid;
int len=(Ra+Rb-La-Lb+2)>>1,pos;
swap(b[Lb-1],fni); swap(b[Rb+1],inf);
while(l<=r){
mid=(l+r)>>1;
pos=Lb+len-(mid-La+1);
if(pos+1<Lb){r=mid-1; continue;}
if(pos-1>Rb){l=mid+1; continue;}
if(b[pos]<=a[mid]&&a[mid]<=b[pos+1]){
swap(b[Lb-1],fni); swap(b[Rb+1],inf);
return a[mid];
}
if(b[pos]>a[mid])l=mid+1;
else r=mid-1;
}
swap(b[Lb-1],fni); swap(b[Rb+1],inf);
l=Lb; r=Rb;
swap(a[La-1],fni); swap(a[Ra+1],inf);
while(l<=r){
mid=(l+r)>>1;
pos=La+len-(mid-Lb+1);
if(pos+1<La){r=mid-1; continue;}
if(pos-1>Ra){l=mid+1; continue;}
if(a[pos]<=b[mid]&&b[mid]<=a[pos+1]){
swap(a[La-1],fni); swap(a[Ra+1],inf);
return b[mid];
}
if(a[pos]>b[mid])l=mid+1;
else r=mid-1;
}
swap(a[La-1],fni); swap(a[Ra+1],inf);
}
int main(){
// freopen("median.in","r",stdin);
// freopen("median.out","w",stdout);
read(n); read(m);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=n;i++) read(b[i]);
int opt,x,y,l,r;
while(m--){
read(opt); read(x); read(y); read(l);
if(opt==1){if(x)b[y]=l;else a[y]=l;}
else {
read(r);
printf("%d\n",solve(x,y,l,r));
}
}
return 0;
}
还有一种思路是每次在每个区间取中间值,然后比较大小分类讨论扔掉一部分区间。
Task.3 Sequence
题目大意:定义序列 \(A\) 的价值为 \(gcd(a_1,a_2,a_3,...a_n,B)\),\(B\) 为给出的定值。定义 \(f(i)\) 为所有的长度为 \(n\),由 \(i\) 的约数组成的序列的价值和。给出 \(n,m,B\),求 \(\sum^m_{i=1} f(i)\) 。
数据范围:\(N,B\leq 10^{18},M\leq 2\cdot 10^7\)
考虑某一个 \(i\) 的约数组成的序列,若 \(i\) 有 \(c_i\) 个约数,\(i\) 对应得序列数量为 \(c_i^n\),每个序列的价值一定是 \(B\) 的约数。
对于一个质数 \(p\),\(p\) 对应的序列可能的价值取值只有;两种:\(1\) 和 \(p\) (如果 \(p\) 是 \(B\) 的约数),\(1\) 。
明早继续解释。