周末题目选解

2022.11.6

洛谷 P2357 守墓人

题面传送门

为什么选这个?

练习树状数组,线段树(主要是树状数组),这个在之后的阶段会经常用到,是一种常见的数据结构。

题目解释

数据结构裸题。

有一个长度为$n$的数组,有几个操作,需要对某个区间加减操作,对某个区间求和。

由于算法思想就是模板,可以多看看树状数组 / 线段树的题解。

树状数组

Code:

#include<bits/stdc++.h>
#define int long long
#define lowbit(x) x&-x 
using namespace std;
const int N=5e5+10;
int n,m,last,opt,x,y,z,mian;
int sum1[N],sum2[N];
void add(int pos,int x)
{
    for(int i=pos;i<=n;i+=lowbit(i))
        sum1[i]+=x,sum2[i]+=pos*x;
}
long long query(int pos)
{
    long long res=0;
    for(int i=pos;i;i-=lowbit(i))
        res+=(pos+1)*sum1[i]-sum2[i];
    return res;
} 
signed main()
{
    cin >> n >> m;
    for(int i=1;i<=n;i++)
    cin >> x,add(i,x-last),last=x;
    for(int i=1,opt;i<=m;i++)
    {
        cin >> opt;
        switch(opt)
        {
            case 1:cin>>x>>y>>z,add(x,z),add(y+1,-z);break;
            case 2:cin>>z,mian+=z;break;
            case 3:cin>>z,mian-=z;break;
            case 4:cin>>x,cin>>y;printf("%lld\n",query(y)-query(x-1)+(x==1)*mian);break;
            case 5:printf("%lld\n",query(1)+mian);
        }
    }
}
树状数组

线段树

Code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
struct node
{
    ll sum,lazy;
}tre[808666];
ll a[200001],n,f,l,r,k;
void build(ll l,ll r,ll now)
{
    if(l==r)
    {
        tre[now].sum=a[l];
        tre[now].lazy=0;
        return;
    }
    ll mid=(l+r)>>1;
    ll lson=now<<1;
    ll rson=lson|1;
    build(l,mid,lson);
    mid++;
    build(mid,r,rson);
    tre[now].lazy=0;
    tre[now].sum=tre[lson].sum+tre[rson].sum;
}
void down(ll l,ll r,ll now)
{
    if(tre[now].lazy)
    {
        ll k=tre[now].lazy;tre[now].lazy=0;
        ll mid=(l+r)>>1,lson=now<<1,rson=lson|1;
        tre[lson].lazy+=k;tre[rson].lazy+=k;
        tre[lson].sum+=(mid-l+1)*k;
        mid++;
        tre[rson].sum+=(r-mid+1)*k;
    }
}
void add(ll u,ll v,ll l,ll r,ll now,ll it)
{
    if(u<=l&&v>=r)
    {
        tre[now].lazy+=it;
        tre[now].sum+=(r-l+1)*it;
        return;
    }
    if(u>r||v<l) return;
    ll mid=(l+r)>>1,lson=now<<1,rson=lson|1;
    down(l,r,now);
    add(u,min(v,mid),l,mid,lson,it);
    mid++;
    add(max(u,mid),v,mid,r,rson,it);
    tre[now].sum=tre[lson].sum+tre[rson].sum;
}
ll ask(ll u,ll v,ll l,ll r,ll now)
{
    if(u<=l&&v>=r)
    {
        return tre[now].sum;
    }
    if(u>r||v<l) return 0;
    ll mid=(l+r)>>1,lson=now<<1,rson=lson|1;
    down(l,r,now);
    ll re=0;
    re+=ask(u,min(v,mid),l,mid,lson);
    mid++;
    re+=ask(max(u,mid),v,mid,r,rson);
    tre[now].sum=tre[lson].sum+tre[rson].sum;
    return re;
}
int main()
{
    scanf("%lld%lld",&n,&f);
    for(ll i=1;i<=n;++i) scanf("%lld",&a[i]);
    build(1,n,1);
    for(ll i=1;i<=f;++i)
    {
        ll opt;
        scanf("%lld",&opt);
        if(opt == 1)
        {
            scanf("%lld%lld%lld",&l,&r,&k);
            add(l,r,1,n,1,k);
        }
        if(opt == 2)
        {
            scanf("%lld",&k);
            add(1,1,1,n,1,k);
        }
        if(opt == 3)
        {
            scanf("%d",&k);
            add(1,1,1,n,1,-k);
        }
        if(opt == 4)
        {
            scanf("%lld%lld",&l,&r);
            printf("%lld\n",ask(l,r,1,n,1));
        }
        if(opt == 5)
        {
            printf("%lld\n",ask(1,1,1,n,1));
        }
    }
    return 0;
}
线段树

 


Codeforces #829 Make Nonzero Sum (version easy / hard)

题面传送门

 

为什么选这个?

是一个练习思维的一道题,涉及算法的知识很少,相比更考验对于题目的理解,以及“贪心”的策略选择;

此外应该还涉及到构造了吧(((

 

题目解释

给定了一个只有$[-1,0,1]$的数构成的数组,选取其不重不漏的子区间集合$s_i=[l_i,r_i]$使得这些子区间集$s_i$的总和为0。不要求子区间集的数量最小化。

 

  • $s_i = a[l_i] - a[l_{i+1}]+…(+ / -)a[r_i]$
  • $r[i] +1 == l[i+1], 1\leq i < n$
  • $l[1]=1, r[k]=n$

 

即每个数组的值均对应在某个子区间上,而每个子区间内的计算顺序是加减交替的。

如果不存在,输出$-1$,存在则输出一种可能的集合组成方案 

浅析

1. 可以把(非零的)数目个数总数分为奇数偶数来看,显然地有奇数个时,该式子无法求解,直接返回-1;

2. 对于一个数量大于等于3的集合,我们均可以有$a-b+c...=(a+b)+c$,所以对于大于3之上的部分我们是可以去改变组合的,我们只需要考虑的是数目小于等于2的一种情况;

3. 我们允许$[x,x]$形式的存在;

4.  每一组都凑成0,那么最后的结果就为0;

5. 对于$easy version$,只用考虑$[-1,1]$的情况,对于$hard version$,则需要考虑$[-1,0,1]$的情况,对于$easy version$我们只需要连续两个数凑成$0$即可,对于$hard version$,我们可以看每一个区域$[x_1,x_2]$的第二个数$x_2$其前面是否为$0$:如果为0,则令该$0$与其一同出现,即为$[x_1,x_1],[0,x_2]$;否则还是跟着前面那个一起出来就行。

 

Code:

为了拓展大家的视野,提供了两种编码习惯的代码,大家可以自行参考。

参考1
#include <bits/stdc++.h>
#define int long long
using namespace std;

int T;
const int L = 5e5 + 5;
int t, n, k, x, y, z, ans, cnt, a[L], flag[L];

signed main() {
	cin >> T;
	while (T--) {
		int sum = 0;
		cin >> n;
		vector<int> po;
		memset(flag, 0, sizeof(flag));
		for (int i = 1; i <= n; i++) {
			cin >> a[i];
			sum += a[i];
		}
		if (sum % 2) {
			cout << "-1" << endl;
			continue;
		}
		sum /= 2;
		for (int i = 2; i <= n; i++) {
			if (a[i] == 1 && sum > 0 && !flag[i - 1]) {
				flag[i] = true;
				po.push_back(i);
				sum--;
			}
			if (a[i] == -1 && sum < 0 && !flag[i - 1]) {
				flag[i] = true;
				po.push_back(i);
				sum++;
			}
			if (sum == 0) {
				break;
			}
		}
		if (sum != 0) {
			cout << "-1" << endl;
			break;
		}
		ans = 0;
		for (int i = 1; i <= n; i++) {
			if (flag[i + 1]) {
				continue;
			}
			ans++;
		}
		if (sum == 0) {
			cout << ans << endl;
			for (int i = 1; i <= n; i++) {
				if (flag[i + 1]) {
					continue;
				}
				if (flag[i]) {
					cout << i - 1 << " " << i << endl;
				} else {
					cout << i << " " << i << endl;
				}
			}
		}
	}
	return 0;
}
参考代码2
 #include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 2e6 + 10;
int n,num,x,t,flag;
int a[N],b[N],last;
int main()
{ 
	 cin >> t;
	 while(t--)
	 {
		  cin >> n;
		  x=0; num=0; last=0;
		  for(int i=1;i<=n;i++)
		  {
			   b[i]=0;
			   cin >> a[i];
			   if(a[i]==0)
			   {
				    x++;
				    b[i]=1;
				    continue;
			   }
			   num++;
			   if(last != 0)
			   {
				    if(last!=a[i])
				    {
					     b[i] = 1;
					     x++;
				    }
				    last=0;
			   }
			   else
			   {
				    last=a[i];
				    b[i]=1;
				    x++;
			   }
		  }
		  if(num % 2 == 1)
		   printf("-1\n");
		  else 
		  {
			   flag = 0;
			   printf("%d\n",x);
			   for(int i=1;i<=n;i++)
			   {
				    if(b[i])
				    {
					     if(flag)
					     printf("%d\n%d ",i-1,i);
					     else
					     {
						      printf("%d ",i);
						      flag = 1;
					     }
				    }
			   }
			   printf("%d\n",n);
		  }
	 } 
 	return 0; 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章