週末題目選解

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; 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章