1. 程式人生 > 其它 >noip模擬測試31

noip模擬測試31

終於有時間寫部落格了,前面一直咕咕咕都快變成一隻公雞了......這次考試,真的很意外,我在考場上覺得自己打出了T1的正解,樣例一拍就過,還跑得嘎嘎快,然後T2,T3碼了兩個暴力,覺得自己應該能100pts+,結果竟然....爆蛋了,T1思路出現了問題,T2打假了,T3TLE飛起,但是收穫還是有的,以後注意多加思考,看看自己的思路有沒有正確性,還有,一定要用暴力程式進行驗證(暴力不要打假...),樣例真是太水了....

T1 Game

思路:最大得分可以利用線段樹很容易求出,如果沒有字典序最大的限制,那麼這道題就非常簡單,但這只是如果,現在這道題有了雙重限制,聽某巨佬的敘述,得知一般有雙重限制的題一般有兩種思考方向,一種是二分,另一種是主席樹,那麼很顯然

,這道題我們可以考慮二分的思想,具體來說就是我們先通過線段樹求出最大得分,然後考慮這樣一件事情,如果有一對點可以對答案造成貢獻,那麼我們將他們同時刪去必然會使更新後的最大得分-1,這樣我們就可以進行二分查找了,對於可以造成貢獻的點,我們二分出他可以對應的最大的另外一個點,具體實現見程式碼:

AC_code


#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
#define lc (rt<<1)
#define rc (rt<<1|1)
using namespace std;
const int N=200010;
const int INF=1e9+10;
int n;
int a[N],b[N];
multiset<int> sm;
ii read()
{
	int x=0;
	bool f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			f=0;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-x);
}
struct Segment_Tree
{
	int A[N<<2],B[N<<2],sum[N<<2];
	iv pp(int rt)
	{
		int u=min(A[lc],B[rc]);
		sum[rt]=sum[lc]+sum[rc]+u;
		A[rt]=A[lc]+A[rc]-u;
		B[rt]=B[lc]+B[rc]-u;
	}
	iv insert(int rt,int l,int r,int p,int x,int y)
	{
		if(l==r)
		{
			A[rt]+=x;
			B[rt]+=y;
			return;
		}
		int mid=(l+r)>>1;
		if(mid>=p)
			insert(lc,l,mid,p,x,y);
		else
			insert(rc,mid+1,r,p,x,y);
		pp(rt);
	}
}T;
int main()
{
	const int N=100000;
    n=read();
    for(re i=1;i<=n;i++)
    {
    	a[i]=read();
    	T.insert(1,1,N,a[i],1,0);
    }
    for(re i=1;i<=n;i++)
    {
    	b[i]=read();
    	T.insert(1,1,N,b[i],0,1);
    	sm.insert(b[i]);
    }
    int ans=T.sum[1];
    for(re i=1;i<=n;i++)
    {
    	int l=a[i]+1,r=*(--sm.end()),out=-1;
    	T.insert(1,1,N,a[i],-1,0);
    	while(l<=r)
    	{
    		int mid=(l+r)>>1;
    		T.insert(1,1,N,mid,0,-1);
    		if(T.sum[1]+1==ans)
    		{
    			l=mid+1;
    			out=mid;
    		}
    		else
    			r=mid-1;
    		T.insert(1,1,N,mid,0,1);
    	}
    	if(out!=-1)
    	{
    		--ans;
    		sm.erase(sm.find(out));
    		T.insert(1,1,N,out,0,-1);
    		printf("%d ",out);
    	}
  	else
 
    	{
    		l=1,r=a[i],out;
    		while(l<=r)
    		{
    			int mid=(l+r)>>1;
    			T.insert(1,1,N,mid,0,-1);
    			if(T.sum[1]==ans)
    			{
    				l=mid+1;
    				out=mid;
    			}
    			else
    				r=mid-1;
    			T.insert(1,1,N,mid,0,1);
    		}
    		printf("%d ",out);
    		sm.erase(sm.find(out));
    		T.insert(1,1,N,out,0,-1);
    	}
    }
    return 0;
}

T2 Time

這道題,或者說是這一種對數列進行排列的題,我是比較不自信的,因為自己不怎麼有思路,導致我沒有留出時間進行思考,這也是一個教訓,逃避不是永久的辦法,只有自己敢去想,去做,才能解決問題。
思路:這道題要求小的數字往兩邊靠,那很顯然,如果要做到最小操作次數就要比較向左還是向右移動更優,可以利用線段樹或者樹狀陣列實現,演算法的正確性在於,當我們將小數字向邊緣移動的時候,我們都會將比他大的數字向中心移動,所以我們只需要利用貪心的思想只考慮使當前最優即可。

AC_code

#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
#define lc (rt<<1)
#define rc (rt<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int N=1e5+10;
const int INF=1e9+10;
int n;
deque<int>q[N];
ii read()
{
	int x=0;
	bool f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			f=0;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-x);
}
struct Segment_Tree
{
	int sum[N<<2];
	iv pp(int rt)
	{
		sum[rt]=sum[lc]+sum[rc];
	}
	iv insert(int rt,int l,int r,int p,int z)
	{
		if(l==r)
		{
			sum[rt]+=z;
			return;
		}
		if(mid>=p)
			insert(lc,l,mid,p,z);
		else
			insert(rc,mid+1,r,p,z);
		pp(rt);
	}
	ii query(int rt,int l,int r,int L,int R)
	{
		if(L>R)
			return 0;
		if(L<=l&&r<=R)
			return sum[rt];
		if(mid>=R)
			return query(lc,l,mid,L,R);
		if(mid<L)
			return query(rc,mid+1,r,L,R);
		return query(lc,l,mid,L,R)+query(rc,mid+1,r,L,R);
	}
}T;
int main()
{
	int ans=0;
	n=read();
	for(re i=1;i<=n;i++)
	{
		q[read()].push_back(i);
		T.insert(1,1,n,i,1);	
	}
	for(re i=1;i<=n;i++)
	{
		while(!q[i].empty())
		{
			int l=q[i].front(),r=q[i].back();
			int sl=T.query(1,1,n,1,l-1),sr=T.query(1,1,n,1,n)-T.query(1,1,n,1,r);
			if(sl<=sr)
			{
				ans+=sl;
				T.insert(1,1,n,l,-1);
				q[i].pop_front();
			}						
			else
			{
				ans+=sr;
				T.insert(1,1,n,r,-1);
				q[i].pop_back();
			}
		}
	}
	printf("%d\n",ans);
    return 0;
}

T3 Cover

好吧,這道題怪我沒有認真聽課,區間兩兩之間只有包含和不相交的關係滿足這個條件,他們的包含關係一定會構成一顆樹,那麼這道題顯然就是一個樹形DP(而不是我寫的區間DP),記 \(f_{i,j}\)表示在 i 為根的子樹中點被覆蓋的最多次數為 j 的最優答案,轉移
的時候首先將所有子樹的答案直接合並,然後考慮點 i 的貢獻 \(f_{i,j}=max(f_{i,j},f_{i,j-1}+val)\) , 這樣樸素的DP方程顯然時間複雜度會爆炸,考慮優化,我們考慮答案更新的過程,我們利用包含關係構建一顆樹,那麼我們將子樹合併的時候,當前的節點會儲存多個val,包括自己本身的,還有自己的兒子合併得到的,這種答案屬於同一層,我們需要不同層之間的轉移。也就是到達跟節點的時候我們需要不斷從左右區間中分別拿出最大值,這就是我們需要的答案,那麼我們就可以利用一個 set ,通過一個合併的操作插入值並排序,具體實現見程式碼:

AC_code


#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
#define lc (rt<<1)
#define rc (rt<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int N=1e5+10;
const int INF=1e9+10;
int n;
deque<int>q[N];
ii read()
{
	int x=0;
	bool f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			f=0;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-x);
}
struct Segment_Tree
{
	int sum[N<<2];
	iv pp(int rt)
	{
		sum[rt]=sum[lc]+sum[rc];
	}
	iv insert(int rt,int l,int r,int p,int z)
	{
		if(l==r)
		{
			sum[rt]+=z;
			return;
		}
		if(mid>=p)
			insert(lc,l,mid,p,z);
		else
			insert(rc,mid+1,r,p,z);
		pp(rt);
	}
	ii query(int rt,int l,int r,int L,int R)
	{
		if(L>R)
			return 0;
		if(L<=l&&r<=R)
			return sum[rt];
		if(mid>=R)
			return query(lc,l,mid,L,R);
		if(mid<L)
			return query(rc,mid+1,r,L,R);
		return query(lc,l,mid,L,R)+query(rc,mid+1,r,L,R);
	}
}T;
int main()
{
	int ans=0;
	n=read();
	for(re i=1;i<=n;i++)
	{
		q[read()].push_back(i);
		T.insert(1,1,n,i,1);	
	}
	for(re i=1;i<=n;i++)
	{
		while(!q[i].empty())
		{
			int l=q[i].front(),r=q[i].back();
			int sl=T.query(1,1,n,1,l-1),sr=T.query(1,1,n,1,n)-T.query(1,1,n,1,r);
			if(sl<=sr)
			{
				ans+=sl;
				T.insert(1,1,n,l,-1);
				q[i].pop_front();
			}						
			else
			{
				ans+=sr;
				T.insert(1,1,n,r,-1);
				q[i].pop_back();
			}
		}
	}
	printf("%d\n",ans);
    return 0;
}