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;
}