1. 程式人生 > 實用技巧 >我們仍未知道那天所看見的題的解法 - 2

我們仍未知道那天所看見的題的解法 - 2

雖然這年頭沒人信luogu難度評分,但還是寫上吧

【目錄】

P3620 [APIO/CTSC 2007]資料備份

【分析過程】

可以總結以下兩點:

  • 辦公樓的排列為直線而非環形
  • 兩個相鄰的辦公樓永遠是最優的

先將 \(n\) 個數讀入,然後算出 \(n-1\) 個間隔的長度,加入小根堆

如果我們只是一股腦的找當前能選的最小值,然後加進 \(ans\) 的話,肯定是不對的

例如下面這組資料:

5 2
0 2 12 14 15

首先進行預處理:

2 10 2 1

我們發現首先程式會選擇 \(1\),然後兩個二都不能選了,最後肯定選 \(10\),答案為 \(11\)

但是我們用腳指頭都能算出來正確答案\(4\)

怎麼辦呢?

我們發現對於一個位置 \(i\)

  • 要麼選 \(i\)
  • 要麼不選 \(i\),而是選擇 \(i-1\)\(i+1\)

所以發現一種情況不是最優的之後我們要立即將其改變為另一種情況

考慮反悔貪心,對於一個位置 \(i\),選擇它之後將其值變為左右兩邊的值的和減去這個點的值

這也就是差值,如果這個數被選擇,那麼就意味著反悔,即原來的決策不是最優的

\(v_i\) 表示 \(i\) 這個位置的值

原始: \(v_{i-1}\;\;\;\;v_i\;\;\;\;v_{i+1}\)

選擇 \(v_i\)\(v_{i-1}\;\;\;\;v_{i-1}+v_{i+1}-v_i\;\;\;\;v_{i+1}\)

\(ans=v_i\)

發現不是最優情況: \(v_{i-1}\;\;\;\;v_{i-1}+v_{i+1}-v_i\;\;\;\;v_{i+1}\)

\(ans=v_i+v_{i-1}+v_{i+1}-v_i\)

\(ans=v_{i-1}+v_{i+1}\)

反悔完成

【程式碼實現】

#include<iostream>
#include<queue>
#define N 1000001
#define int long long
using namespace std;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
int n,m,rd[N],in[N],ans,will,doing;
struct node{
	node *pre,*nxt;
	int v,num;
};
node *place[N];
inline void del(int n){
    if(n==-1) return;
	place[n]->pre->nxt=place[n]->nxt;
	place[n]->nxt->pre=place[n]->pre;
	delete place[n];
	place[n]=NULL;
}
signed main(){
	cin>>n>>m>>rd[0];
	for(int i=1;i<n;i++){
		cin>>rd[i];
		in[i]=rd[i]-rd[i-1];
		q.push(make_pair(in[i],i));
		node *nd=new node;
		place[i]=nd;
		nd->v=in[i];nd->num=i;
		if(i!=1){
			nd->pre=place[i-1];
			place[i-1]->nxt=nd;
		}
	}
	node *nd=new node;
	nd->num=-1;nd->v=2147483647;
	place[1]->pre=nd;
	place[n-1]->nxt=nd;
	while(1){
		if(q.empty()) break;
		will=q.top().second;
		if(place[will]==NULL){
			q.pop();
			continue;
		}
		ans+=q.top().first;
		doing+=1;
		if(doing==m) break;
		place[will]->v=place[will]->pre->v+place[will]->nxt->v-place[will]->v;
		del(place[will]->pre->num);del(place[will]->nxt->num);
		q.pop();
		q.push(make_pair(place[will]->v,will));
	}
	cout<<ans;
}

CF911D Inversion Counting

【題意】

給定一個序列

每次操作翻轉一個區間 \([l,r]\)

每次翻轉後整個序列逆序對的奇偶性

【分析過程】

首先看到翻轉區間我們可以想到Splay可以勝任

看到逆序對可以想到歸併排序

但是根據 (CF的題目都是思維題) 時間複雜度可以很輕易地判斷出這顯然是不行的

考慮這道題目讓我們求什麼

顯然,我們並不需要求出逆序對的數目,而只需要求它的奇偶性

對於一個要被翻轉的區間 \([l,r]\)

顯然長度 \(len=r-l+1\)

全部數對的數目是:\(num=\frac{len(len-1)}{2}\)

對於這個區間:

  • 其全部的正序對在翻轉後會變成逆序對

  • 其全部的逆序對在翻轉後會變成正序對

重點來了

如果 \(num\) 是一個偶數,分類討論其逆序對的奇偶性的兩種情況

  • 奇數,那麼其正序對的數目也是奇數,翻轉後奇偶性不變

  • 偶數,顯然翻轉後奇偶性不變

如果 \(num\) 是一個奇數,其逆序對的奇偶性也有兩種情況

  • 奇數,那麼其正序對的數目是偶數,翻轉後奇偶性改變

  • 偶數,那麼其正序對的數目是奇數,翻轉後奇偶性改變

【總結】

如果 \(num\) 是偶數,那麼不管怎樣奇偶性都不變,如果是奇數,那麼不管怎麼樣奇偶性都會改變

【程式碼實現】

#include<iostream>
#include<algorithm>
using namespace std;
int a[500001],r[500001],len,ans,e,L,R,t,num;
void msort(int s,int e){
	if(s==e) return;
	long long int mid=(s+e)/2;
	msort(s,mid);msort(mid+1,e);
	int i=s,j=mid+1,k=s;
	while(i<=mid&&j<=e){
		if(a[i]<=a[j]){
			r[k]=a[i];
			k+=1;i+=1;
		}
		else{
			r[k]=a[j];
			k+=1;j+=1;
			ans+=mid-i+1;
		}
	}
	while(i<=mid){
		r[k]=a[i];
		k+=1;i+=1;
	}
	while(j<=e){
		r[k]=a[j];
		k+=1;j+=1;
	}
	for(int i=s;i<=e;i++)
		a[i]=r[i];
}
int main(){
	cin>>num;
	for(int i=1;i<=num;i++){
		cin>>a[i];
	}
	msort(1,num);
	if(ans%2==0) e=1;
	cin>>t;
	while(t--){
		cin>>L>>R;
		len=R-L+1;
		if(len*(len-1)/2%2){
			e=!e;
		}
		cout<<(e?"even\n":"odd\n");
	}
}

為什麼我的歸併排序時間複雜度假了QAQ

CF631C Report

【分析過程】

先看資料範圍:200000,排除 \(O(n^2)\)

然後仔細看題,我們會發現幾個很有趣的地方:

  • 對序列操作不是對 \([l,r]\) 這個區間操作,而是對 \([1,r]\) 操作
  • 這個操作不是區間翻轉,區間加法或其他操作,而是排序

容易發現當有兩次操作

t1 r1
t2 r2

如果 \(r_2\geq r_1\)

那麼顯然第一次的操作是沒有任何用的,因為不管第一次怎麼排序,第二次都會將其覆蓋掉

至此,我們可以輕而易舉的發現這些操作可以用一個單調棧來維護
最後棧中就會是這樣一個樣子:

t1 r1
t2 r2
......
ti ri

其中 \(r_1>r_2>\dots>r_i\)

對於棧中的兩個連續二元組 \((t_1,r_1),(t_2,r_2)\) 其中 \(r_1>r_2\)
我們按照 \(t_1\) 所對應的排序法則排序,然後 \((r_2,r_1]\) 的每個位置對應的數就確定了

因為直接排序複雜度較高,這裡我們用兩個變數 \(le,re\) 作為左右指標來使用

【程式碼實現】

#include<iostream>
#include<algorithm>
#define N 500001
using namespace std;
struct Node{
	int t,r;
}stk[N];
int in[N],n,m,t,r,top,ans[N],le,re,now;
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>in[i];
	for(int i=1;i<=m;i++){
		cin>>t>>r;
		while(top&&stk[top].r<=r) top-=1;
		stk[++top]={t,r};
	}
	re=stk[1].r+1;now=1;
	sort(in+1,in+re);
	for(int i=re-1;i>=1;i--){
		if(i<=stk[now+1].r&&now+1<=top) now+=1;
		if(stk[now].t==1)
			ans[i]=in[--re];
		else
			ans[i]=in[++le];
	}
	for(int i=1;i<=stk[1].r;i++)
		cout<<ans[i]<<" ";
	for(int i=stk[1].r+1;i<=n;i++)
		cout<<in[i]<<" ";
}