[GDCPC2023] Base Station Construction
题目描述
中国移动通信集团广东有限公司深圳分公司(以下简称深圳移动
)于
1999
1999
1999 年正式注册。四年后,广东省大学生程序设计竞赛第一次举办。深圳移动与广东省大学生程序设计竞赛一起见证了广东省计算机行业的兴旺与发展。
在建设通信线路的过程中,信号基站的选址是一个非常关键的问题。某城市从西到东的距离为 n n n 千米,工程师们已经考察了在从西往东 1 , 2 , ⋯ , n 1, 2, \cdots, n 1,2,⋯,n 千米的位置建设基站的成本,分别是 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots, a_n a1,a2,⋯,an。
为了保证居民的通信质量,基站的选址还需要满足 m m m 条需求。第 i i i 条需求可以用一对整数 l i l_i li 和 r i r_i ri 表示( 1 ≤ l i ≤ r i ≤ n 1 \le l_i \le r_i \le n 1≤li≤ri≤n),代表从西往东 l i l_i li 千米到 r i r_i ri 千米的位置之间(含两端)至少需要建设 1 1 1 座基站。
作为总工程师,您需要决定基站的数量与位置,并计算满足所有需求的最小总成本。
【输入格式】
有多组测试数据。第一行输入一个整数 T T T 表示测试数据组数,对于每组测试数据:
第一行输入一个整数 n n n( 1 ≤ n ≤ 5 × 1 0 5 1 \le n \le 5 \times 10^5 1≤n≤5×105)表示城市从西到东的距离。
第二行输入 n n n 个整数 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots, a_n a1,a2,⋯,an( 1 ≤ a i ≤ 1 0 9 1 \le a_i \le 10^9 1≤ai≤109),其中 a i a_i ai 表示在从西往东 i i i 千米的位置建设基站的成本。
第三行输入一个整数 m m m( 1 ≤ m ≤ 5 × 1 0 5 1 \le m \le 5 \times 10^5 1≤m≤5×105)表示需求的数量。
对于接下来 m m m 行,第 i i i 行输入两个整数 l i l_i li 和 r i r_i ri( 1 ≤ l i ≤ r i ≤ n 1 \le l_i \le r_i \le n 1≤li≤ri≤n)表示从西往东 l i l_i li 千米到 r i r_i ri 千米的位置之间(含两端)至少需要建设 1 1 1 座基站。
保证所有数据 n n n 之和与 m m m 之和均不超过 5 × 1 0 5 5 \times 10^5 5×105。
【输出格式】
每组数据输出一行一个整数,表示满足所有需求的最小总成本。
【样例解释】
对于第一组样例数据,最优方案是在从西往东 2 2 2 千米和 5 5 5 千米的位置建设基站。总成本为 2 + 100 = 102 2 + 100 = 102 2+100=102。
对于第二组样例数据,最优方案是在从西往东 2 2 2 千米和 4 4 4 千米的位置建设基站。总成本为 3 + 2 = 5 3 + 2 = 5 3+2=5。
思路:
考虑dp, d p [ i ] dp[i] dp[i] 表示到位置 i i i 为止,不包含自身当前所在基站的且前面所有基站全部合法的最小值。那么对于当前点 i i i,其能从距离当前点最近的的左端点 j j j,开始转移,因此我们预处理出对于每个点的转移区间,然后从转移区间中的最小值进行转移即可,可以使用单调队列或者线段树进行dp优化。
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<ll, 3> ar;
const int mod = 998244353;
const int maxv = 4e6 + 5;
#define endl "\n"
struct node
{
ll l,r,val;
#define l(x) tr[x].l
#define r(x) tr[x].r
#define val(x) tr[x].val
}tr[N*4];
void update(int p)
{
val(p)=min(val(p*2),val(p*2+1));
}
void build(int p,int l,int r)
{
if(l==r){
tr[p]={l,r,0};
return ;
}
l(p)=l,r(p)=r;
int mid=(l+r)/2;
build(p*2,l,mid),build(p*2+1,mid+1,r);
}
void change(int p,int pos,ll x)
{
if(l(p)==r(p)){
val(p)=x;
return ;
}
int mid=(l(p)+r(p))/2;
if(pos<=mid) change(p*2,pos,x);
else change(p*2+1,pos,x);
update(p);
}
ll query(int p,int l,int r)
{
if(l<=l(p)&&r(p)<=r) return val(p);
int mid=(l(p)+r(p))/2;
ll res=1e18;
if(l<=mid) res=min(res,query(p*2,l,r));
if(r>mid) res=min(res,query(p*2+1,l,r));
return res;
}
void solve()
{
int n;
cin>>n;
vector<ll> a(n+5);
for(int i=1;i<=n;i++) cin>>a[i];
vector<int> pre(n+5);
int m;
cin>>m;
int maxr=0;
for(int i=1;i<=m;i++){
int l,r;
cin>>l>>r;
pre[r]=max(pre[r],l);
maxr=max(maxr,r);
}
for(int i=1;i<=n+1;i++){
pre[i]=max(pre[i-1],pre[i]);
}
build(1,0,n+5);
for(int i=1;i<=n+1;i++){
ll tmp=query(1,pre[i-1],i-1);
change(1,i,tmp+a[i]);
}
cout<<query(1,n+1,n+1)<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
cin >> t;
while (t--)
{
solve();
}
system("pause");
return 0;
}
[GDCPC2023] Traveling in Cells
题目描述
有 n n n 个格子排成一行,第 i i i 个格子的颜色为 c i c_i ci,上面放置着一个权值为 v i v_i vi 的球。
您将要在格子中进行若干次旅行。每次旅行时,您会得到旅行的起点 x x x 与一个颜色集合 A = { a 1 , a 2 , ⋯ , a k } \mathbb{A} = \{a_1, a_2, \cdots, a_k\} A={a1,a2,⋯,ak},且保证 c x ∈ A c_x \in \mathbb{A} cx∈A。旅行将从第 x x x 个格子上开始。在旅行期间,如果您在格子 i i i 处,那么您可以向格子 ( i − 1 ) (i - 1) (i−1) 或 ( i + 1 ) (i + 1) (i+1) 处移动,但不能移动到这 n n n 个格子之外。且在任意时刻,您所处的格子的颜色必须在集合 A \mathbb{A} A 中。
当您位于格子 i i i 时,您可以选择将格子上的球取走,并获得 v i v_i vi 的权值。由于每个格子上只有一个球,因此一个格子上的球只能被取走一次。
您的任务是依次处理 q q q 次操作,每次操作形如以下三种操作之一:
- 1 p x 1\; p \; x 1px:将 c p c_p cp 修改为 x x x。
- 2 p x 2\; p \; x 2px:将 v p v_p vp 修改为 x x x。
- 3 x k a 1 a 2 … a k 3\; x\; k\; a_1\; a_2 \; \ldots\; a_k 3xka1a2…ak:给定旅行的起点 x x x 与一个颜色集合 A = { a 1 , a 2 , ⋯ , a k } \mathbb{A} = \{a_1, a_2, \cdots, a_k\} A={a1,a2,⋯,ak}。假设如果进行这样的一次旅行,求出取走的球的权值之和最大是多少。注意,由于我们仅仅假设进行一次旅行,因此并不会真的取走任何球。即,所有询问之间是独立的。
【输入格式】
有多组测试数据。第一行输入一个整数 T T T 表示测试数据组数。对于每组测试数据:
第一行输入两个整数 n n n 和 q q q( 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105, 1 ≤ q ≤ 1 0 5 1 \leq q \leq 10^5 1≤q≤105)表示格子的数量和操作的数量。
第二行输入 n n n 个整数 c 1 , c 2 , … , c n c_1, c_2, \ldots, c_n c1,c2,…,cn( 1 ≤ c i ≤ n 1 \leq c_i \leq n 1≤ci≤n),其中 c i c_i ci 表示第 i i i 个格子的初始颜色。
第三行输入 n n n 个整数 v 1 , v 2 , … , v n v_1, v_2, \ldots, v_n v1,v2,…,vn( 1 ≤ v i ≤ 1 0 9 1 \leq v_i \leq 10^9 1≤vi≤109),其中 v i v_i vi 表示第 i i i 个格子里的球的初始权值。
对于接下来 q q q 行,第 i i i 行描述第 i i i 次操作,格式如下:
- 1 p x 1\; p\; x 1px:保证 1 ≤ p ≤ n 1 \leq p \leq n 1≤p≤n 且 1 ≤ x ≤ n 1 \leq x \leq n 1≤x≤n。
- 2 p x 2\; p\; x 2px:保证 1 ≤ p ≤ n 1 \leq p \leq n 1≤p≤n 且 1 ≤ x ≤ 1 0 9 1 \leq x \leq 10^9 1≤x≤109。
- 3 x k a 1 a 2 … a k 3\; x\; k\; a_1\; a_2\; \ldots \; a_k 3xka1a2…ak:保证 1 ≤ x ≤ n 1 \leq x \leq n 1≤x≤n 且 1 ≤ a 1 < a 2 < … < a k ≤ n 1 \leq a_1 < a_2 < \ldots < a_k \leq n 1≤a1<a2<…<ak≤n 且 c x ∈ { a 1 , a 2 , ⋯ , a k } c_x \in \{a_1, a_2, \cdots, a_k\} cx∈{a1,a2,⋯,ak}。
保证所有数据 n n n 之和与 q q q 之和均不超过 3 × 1 0 5 3 \times 10^5 3×105,且所有数据 k k k 之和不超过 1 0 6 10^6 106。
【输出格式】
对于每次操作 3 3 3 输出一行一个整数,表示取走的球的权值之和的最大值。
思路:
首先对于操作三,显然我们是拿的球越多越好,因为总的数据范围是 1e5 级别,所以考虑在 log 的时间复杂度内求出每次的答案。那么考虑二分,因为显然,边界具有单调性,对于
i
<
j
i<j
i<j , 若
i
i
i 合法,那么
j
j
j 肯定合法,所以我们可以去二分得到左右边界,考虑如何 check ,若这整个区间中给定合法球的个数等于区间长度时,那么肯定合法,那么继续考虑如何维护区间中球的个数,显然我们可以对每个颜色开一个线段树进行维护,若当前位置有球则是 1 ,没有球则是 0,为什么使用线段树,因为操作一涉及到单点修改,刚刚所说的操作涉及到区间查询,所以上述操作只有线段树支持。对于
n
n
n 种颜色的维护则使用动态开点即可。
最后使用线段树或者树状数组进行区间和的维护即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<ll, 3> ar;
const int mod = 998244353;
const int maxv = 4e6 + 5;
#define endl "\n"
int tot,rt[200005];
struct node{
ll sum,lc,rc;
}tree[12000005];
int build(){
tot++;
return tot;
}
void pushup(int x){
tree[x].sum=tree[tree[x].lc].sum+tree[tree[x].rc].sum;
return;
}
void change(int x,int l,int r,int w,ll v){
if(l==r){
tree[x].sum+=v;
return;
}
int mid=(l+r)/2;
if(w<=mid){
if(!tree[x].lc)tree[x].lc=build();
change(tree[x].lc,l,mid,w,v);
}
else{
if(!tree[x].rc)tree[x].rc=build();
change(tree[x].rc,mid+1,r,w,v);
}
pushup(x);
return;
}
ll query(int x,int l,int r,int l0,int rr){
if(r<l0||l>rr)return 0;
if(l>=l0&&r<=rr){
return tree[x].sum;
}
int mid=(l+r)/2;
ll res=0;
res=res+query(tree[x].lc,l,mid,l0,rr);
res=res+query(tree[x].rc,mid+1,r,l0,rr);
return res;
}
int n,q;
struct MIT
{
ll tr[N];
int lowbit(int x) {
return x & (-x);
}
void add(int u, int v) {
for (int i = u; i <=n; i += lowbit(i)) {
tr[i] += v;
}
}
ll query(int x) {
ll res = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
res += tr[i];
}
return res;
}
};
MIT z;
int c[N];
ll a[N];
void solve()
{
cin>>n>>q;
vector<int> vis(n+5);
for(int i=1;i<=n;i++) rt[i]=build();
for(int i=1;i<=n;i++){
cin>>c[i];
change(rt[c[i]],1,n,i,1);
}
for(int i=1;i<=n;i++) cin>>a[i],z.add(i,a[i]);
auto check=[&](int x,int pos,vector<int> &v)
{
if(x>pos) swap(x,pos);
int sum=pos-x+1;
int cnt=0;
for(auto &vi: v){
cnt+=query(rt[vi],1,n,x,pos);
}
return cnt>=sum;
};
while(q--){
int op;
cin>>op;
if(op==1){
int p,x;
cin>>p>>x;
change(rt[c[p]],1,n,p,-1);
change(rt[x],1,n,p,1);
c[p]=x;
}
else if(op==2){
int p,x;
cin>>p>>x;
z.add(p,x-a[p]);
a[p]=x;
}
else{
int x,k;
cin>>x>>k;
vector<int> v(k);
for(auto &vi: v) cin>>vi;
int l=1,r=x;
int l1=x,r1=x;
while(l<=r){
int mid=(l+r)/2;
if(check(x,mid,v)){
l1=mid;
r=mid-1;
}
else{
l=mid+1;
}
}
l=x,r=n;
while(l<=r){
int mid=(l+r)/2;
if(check(x,mid,v)){
r1=mid;
l=mid+1;
}
else{
r=mid-1;
}
}
ll ans=z.query(r1)-z.query(l1-1);
cout<<ans<<endl;
}
}
for(int i=0;i<=tot;i++){
tree[i].lc=tree[i].rc=tree[i].sum=0;
}
for(int i=1;i<=n;i++) z.tr[i]=0;
tot=0;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
cin >> t;
while (t--)
{
solve();
}
system("pause");
return 0;
}