1. 程式人生 > 其它 >6.10考試總結(NOIP模擬6)

6.10考試總結(NOIP模擬6)

前言

就這題考的不咋樣果然還挺難改的。。

T1 辣雞

題目描述

辣雞ljh NOI之後就退役了,然後就滾去學文化課了。

然而在上化學課的時候,數學和化學都不好的ljh卻被一道簡單題難住了,受到了大佬的嘲笑。

題目描述是這樣的:

在一個二維平面上有一層水分子,請問形成了多少個氫鍵?

這個二維平面可以看做一個類似棋盤的東西,每個格子可以容納一個水分子,左下角的格子為(0,0),這個格子右邊的格子為(1,0),上方格子為(0,1),以此類推。

辣雞ljh當然不會做了,所以他來求助JeremyGou,JeremyGou一眼就看穿了真相,並想用這道題來考一考正在做NOIP模擬賽的你。

注:在本題中,我們認為一個水分子能與和它曼哈頓距離為2且直線距離小於2的其他格子形成氫鍵。

輸入格式

一個整數n

接下來n行,每行給出四個整數x1,y1,x2,y2

表示以(x1,y1)為左下角,(x2,y2)為右上角的矩形中每個格子都有一個水分子。

給出的所有矩形沒有交集。

輸出格式

一個整數,表示氫鍵的數量。

樣例

樣例輸入1

3
0 0 0 0
0 1 1 2
2 2 2 3

樣例輸出1

5

樣例輸入2

10
1 8 8 9
0 3 10 7
0 0 7 0
0 2 9 2
4 10 8 10
10 0 10 2
0 10 0 10
8 0 9 1
0 8 0 9
9 8 10 8

樣例輸出2

157

資料範圍與提示

樣例1解釋

左圖為水分子的排布,右圖中的綠色線條表示氫鍵。

前言

我做夢都沒想到這題正解是模擬,打模擬賽的時候看錯題面以為是\(n\times n\)

的矩陣,喜提0pts

解題思路

氫鍵的數量計算起來無非主要就是兩種情況:

  • 整個矩陣裡面的
  • 各個矩陣之間相鄰的

整個矩陣裡的比較好算:

\(\sum\limits_{i=1}^{n}(2\times q[i].x_2-q[i].x_1)\times(q[i].y_2-q[i].y_1)\)

主要是矩陣之間的比較難整,鑑於x和y相鄰的情況差不多,以下只講述x的情況,前提是兩個矩形的比較近的兩個橫座標相差為1。假設矩形A為當前矩形,B為正在匹配的矩形分為4種:

  1. B的邊長大於A的(A的兩端都在B的\([x_1,x_2]\)區間中):產生氫鍵數為 \(2 \times A\) 的縱向長度,在此還可以同時處理一下A和B矩形邊長相等的情況,只不過比邊長相等的長度多了一兩個鍵,不難發現,A的兩個角上也可以分別連到B上。

  2. A的邊長大於B的(B的兩端都在A的\([x_1,x_2]\)區間中):產生氫鍵的數量處理方法與上面差不多,只不過不需要判斷等於的情況了。

  3. A的下端點在B的區間內,但上端點不在(或者反之):產生氫鍵數量就是兩個矩形交叉的部分以及邊角處。

  4. A與B的矩陣的角相鄰:直接加上1就好了。

當然,我們還需要一些優化,不難發現,對於矩陣的橫座標排序後滿足單調性,如果j+1無法與i匹配那麼j也不能,直接break。

code



#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,ans;
struct Ques
{
	int x,y,x2,y2;
}q[N];
bool comp(Ques x,Ques y)
{
	if(x.x==y.x)
		return x.y<x.y;
	return x.x<y.x;
}
#undef int
int main()
{
	#define int register long long
	#define ll long long
	scanf("%lld",&n);
	if(n==1)
	{
		int i=1;
		scanf("%lld%lld%lld%lld",&q[i].x,&q[i].y,&q[i].x2,&q[i].y2);
		ans+=(q[i].x2-q[i].x)*(q[i].y2-q[i].y)*2;
		printf("%lld",ans);
		return 0;
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%lld%lld%lld%lld",&q[i].x,&q[i].y,&q[i].x2,&q[i].y2);
		ans+=(q[i].x2-q[i].x)*(q[i].y2-q[i].y)*2;
	}
	sort(q+1,q+n+1,comp);
	for(int i=1;i<n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			if(q[j].x-q[i].x2>1)
				break;
			if(q[i].x-q[j].x2==1||q[j].x-q[i].x2==1)
			{
				if(q[i].y2<=q[j].y2&&q[i].y>=q[j].y)
				{
					ans+=2*(q[i].y2-q[i].y);
					if(q[i].y>q[j].y)
						ans++;
					if(q[i].y2<q[j].y2)
						ans++;
				}
				else if(q[j].y2<q[i].y2&&q[j].y>q[i].y)
				{
					ans+=2*(q[j].y2-q[j].y)+2;
				}
				else if(q[i].y2>=q[j].y&&q[i].y2<=q[j].y2)
				{
					ans+=2*(q[i].y2-q[j].y);
					if(q[i].y<q[j].y)
						ans++;
					if(q[i].y2<q[j].y2)
						ans++;
				}
				else if(q[i].y<=q[j].y2&&q[i].y>=q[j].y)
				{
					ans+=2*(q[j].y2-q[i].y);
					if(q[i].y>q[j].y)
						ans++;
					if(q[i].y2>q[j].y2)
						ans++;
				}
			}
			if(q[i].y-q[j].y2==1||q[j].y-q[i].y2==1)
			{
				if(q[i].x2<=q[j].x2&&q[i].x>=q[j].x)
				{
					ans+=2*(q[i].x2-q[i].x);
					if(q[i].x>q[j].x)
						ans++;
					if(q[i].x2<q[j].x2)
						ans++;
				}
				else if(q[j].x2<q[i].x2&&q[j].x>q[i].x)
				{
					ans+=2*(q[j].x2-q[j].x)+2;
				}
				else if(q[i].x2>=q[j].x&&q[i].x2<=q[j].x2)
				{
					ans+=2*(q[i].x2-q[j].x);
					if(q[i].x<q[j].x)
						ans++;
					if(q[i].x2<q[j].x2)
						ans++;
				}
				else if(q[i].x<=q[j].x2&&q[i].x>=q[j].x)
				{
					ans+=2*(q[j].x2-q[i].x);
					if(q[i].x>q[j].x)
						ans++;
					if(q[i].x2>q[j].x2)
						ans++;
				}
			}
			if((q[i].x-q[j].x2==1||q[j].x-q[i].x2==1)&&(q[i].y-q[j].y2==1||q[j].y2-q[i].y==1))
				ans++;
		}
	}
	printf("%lld",ans);
	return 0;
}

T2 模板

題面描述

辣雞ljh NOI之後就退役了,然後就滾去學文化課了。

他每天都被katarina大神虐,仗著自己學過一些姿勢就給katarina大神出了一道題。

有一棵 n 個節點的以 1 號節點為根的樹,每個節點上有一個小桶,節點u上的小桶可以容納 個小球,ljh每次可以給一個節點到根路徑上的所有節點的小桶內放一個小球,如果這個節點的小桶滿了則不能放進這個節點,在放完所有小球之後就企圖去刁難katarina大神,讓katarina大神回答每個節點的小桶內的小球有多少種顏色。

然而katarina大神一眼就秒掉了,還說這就是一道傻逼模板題。

現在katarina大神想考考即將參加NOIP2019的你能不能回答上辣雞ljh的問題。

輸入格式

第一行,一個整數n,樹上節點的數量。

接下來n-1行,每行兩個整數u, v,表示在u, v之間有一條邊。

接下來一行n個整數,\(k_1\sim k_n\)表示每個節點上的小桶數量。

下一行是一個整數m,表示ljh進行的運算元量。

接下來m行,每行兩個整數x, c,分別表示進行操作的節點和小球顏色。

下一行是一個整數Q,表示你需要回答的詢問數。

接下來Q行,每行一個整數x,表示一個詢問。

輸出格式

對於每個詢問輸出一行表示這個詢問的答案、

樣例

樣例輸入1

5
1 2
2 3
3 4
2 5
2 1 1 1 1
2
2 1
4 2
3
1
3
5 

樣例輸出1

2
1
0 

樣例輸入2

10
3 10
2 5
3 2
2 6
1 9
8 7
7 4
3 8
3 1
15 47 23 22 9 16 45 39 21 13
10
10 7
9 3
5 1
5 2
9 4
10 9
2 4
10 1
2 6
7 9
3
1
2
3

樣例輸出2

7
4
6 

資料範圍與提示

前言

考試的時候我也不知道咋回事,看著看著就看成小球的個數了。。。喜提0pts。

解題思路

線段樹合併,啟發式合併(說實話,我感覺和線段樹合併沒啥關係),有億點難打,當然強者都去拿平衡樹左右旋了,我這。。。

建一棵權值線段樹,以時間為權值,在樹上分別儲存顏色的種類數個數

因為顏色會有負數所以我們需要離散化一下。並且將顏色與時間用pair封裝壓入vector陣列(防止MLE)。

先進行一邊dfs,求出各個節點的重兒子記為son[x]。

void dfs1(int x,int fa)
{
	siz[x]=v[x].size();
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa)
			continue;
		dfs1(to,x);
		siz[x]+=siz[to];
		if(siz[son[x]]<siz[to])
			son[x]=to;
	}
}

然後再進行dfs,以dfs的順序來更新,先遍歷子樹進行處理,再遍歷完之後清空處重兒子子節點的樹以便重複利用空間防止對於以後的計算造成影響。對於重兒子的資料我們要重複利用。

然後先將現在的節點自身加入到線段樹裡,然後再將所有子節點(除重兒子之外的,畢竟重兒子的數值已經在樹裡了)

再以s[x](小桶最大裝載量)為限制進行查詢詢問。最後將自己兒子的所有操作壓入自己的vector陣列便於自己的爸爸節點處理。

void dfs2(int x,int fa)
{
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa||to==son[x])
			continue;
		dfs2(to,x);
		clear(to);
	}
	if(son[x])
		dfs2(son[x],x);
	add(x);
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa||to==son[x])
			continue;
		add(to);
	}
	ans[x]=ask(1,1,m,s[x]);
	if(son[x])
	{
		move(x,son[x]);
		swap(v[x],v[son[x]]);
		for(int i=head[x];i;i=nxt[i])
		{
			int to=ver[i];
			if(to==fa)
				continue;
			move(to,x);
		}
	}
}

對於更新,laz標記下放以及查詢這一類線段樹基本操作在此不做過多說明,主要講一下將節點的數值存入線段樹的操作:

用一個tim陣列儲存每個顏色之前出現的最晚時間,然後對於以下兩種情況分別進行處理:

  1. tim[clo]為零:也就是說該顏色從未出現過,我們增加這種顏色下表為tim

  2. 有這種顏色並且最晚大於當前要增加的時間:先將最晚的那個時間種類減去,然後加上現在的種類。

最後對於以上兩種特殊情況以及最普通的出現且時間早於當前時間的情況一起將數量加一。

void add(int x)
{
	for(int i=0;i<v[x].size();i++)
	{
		int col=v[x][i].first,time=v[x][i].second;
		if(!tim[col])
		{
			update(1,1,m,time,1,0);
			tim[col]=time;
		}
		else if(time<tim[col])
		{
			update(1,1,m,tim[col],-1,0);
			update(1,1,m,time,1,0);
			tim[col]=time;
		}
		update(1,1,m,time,0,1);
	}
}

講解到此結束,主要還是考驗碼力QAQ,是我太菜了。。。

code

#include<bits/stdc++.h>
#define ls x<<1
#define rs x<<1|1
using namespace std;
const int N=1e6+10;
int n,m,cnt,Q,s[N],tas[N],clo[N],ans[N];
int ys[N],siz[N],son[N<<2],tre[N<<2],tim[N],laz[N<<2];
int edg_tot,head[N],ver[N<<1],nxt[N<<1];
vector<pair<int,int > > v[N];// color time
void add_edge(int x,int y)
{
	ver[++edg_tot]=y;
	nxt[edg_tot]=head[x];
	head[x]=edg_tot;
}
void dfs1(int x,int fa)
{
	siz[x]=v[x].size();
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa)
			continue;
		dfs1(to,x);
		siz[x]+=siz[to];
		if(siz[son[x]]<siz[to])
			son[x]=to;
	}
}
void clear(int x)
{
	tre[1]=siz[1]=0;
	laz[1]=1;
	for(int i=0;i<v[x].size();i++)
		tim[v[x][i].first]=0;
}
void push_down(int x)
{
	if(!laz[x])
		return ;
	tre[ls]=tre[rs]=siz[ls]=siz[rs]=laz[x]=0;
	laz[ls]=laz[rs]=1;
}
void push_up(int x)
{
	tre[x]=tre[ls]+tre[rs];
	siz[x]=siz[ls]+siz[rs];
}
void update(int x,int l,int r,int pos,int val,int size)
{
	siz[x]+=size;
	tre[x]+=val;
	if(l==r)
		return ;
	push_down(x);
	int mid=(l+r)>>1;
	if(pos<=mid)
		update(ls,l,mid,pos,val,size);
	else
		update(rs,mid+1,r,pos,val,size);
	push_up(x);
}
void add(int x)
{
	for(int i=0;i<v[x].size();i++)
	{
		int col=v[x][i].first,time=v[x][i].second;
		if(!tim[col])
		{
			update(1,1,m,time,1,0);
			tim[col]=time;
		}
		else if(time<tim[col])
		{
			update(1,1,m,tim[col],-1,0);
			update(1,1,m,time,1,0);
			tim[col]=time;
		}
		update(1,1,m,time,0,1);
	}
}
void move(int x,int y)
{
	for(int i=0;i<v[x].size();i++)
		v[y].push_back(v[x][i]);
	v[x].clear();
}
int ask(int x,int l,int r,int rank)
{
	if(rank<=0)
		return 0;
	if(l==r)
		return tre[x];
	push_down(x);
	int mid=(l+r)>>1;
	if(rank>=siz[ls])
		return tre[ls]+ask(rs,mid+1,r,rank-siz[ls]);
	return ask(ls,l,mid,rank);
}
void dfs2(int x,int fa)
{
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa||to==son[x])
			continue;
		dfs2(to,x);
		clear(to);
	}
	if(son[x])
		dfs2(son[x],x);
	add(x);
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa||to==son[x])
			continue;
		add(to);
	}
	ans[x]=ask(1,1,m,s[x]);
	if(son[x])
	{
		move(x,son[x]);
		swap(v[x],v[son[x]]);
		for(int i=head[x];i;i=nxt[i])
		{
			int to=ver[i];
			if(to==fa)
				continue;
			move(to,x);
		}
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1,x,y;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add_edge(x,y);
		add_edge(y,x);
	}
	for(int i=1;i<=n;i++)
		scanf("%d",&s[i]);
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&tas[i],&clo[i]);
		ys[i]=clo[i];
	}
	sort(ys+1,ys+m+1);
	cnt=unique(ys+1,ys+m+1)-ys-1;
	for(int i=1;i<=m;i++)
		clo[i]=lower_bound(ys+1,ys+cnt+1,clo[i])-ys;
	for(int i=1;i<=m;i++)
		v[tas[i]].push_back(make_pair(clo[i],i));
	dfs1(1,0);
	memset(siz,0,sizeof(siz));
	dfs2(1,0);
	scanf("%d",&Q);
	for(int i=1,x;i<=Q;i++)
	{
		scanf("%d",&x);
		printf("%d\n",ans[x]);
	}
	return 0;
}

T3 大佬

題目描述

辣雞ljh NOI之後就退役了,然後就滾去學文化課了。

他發現katarina大佬真是太強了,於是就學習了一下katarina大佬的做題方法。

比如這是一本有n道題的練習冊,katarina大佬每天都會做k道題。

第一天做第\(1\sim k\)題,第二天做第 \(2\sim k+1\) 題……第 \(n-k+1\)天做第\(n-k+1 \sim n\) 道題。

但是辣雞 ljh 又不想太累,所以他想知道katarina大佬做完這本練習冊的勞累度。

每道題有它的難度值,假設今天katarina大佬做的題目中最大難度為t,那麼今天katarina大佬的勞累度就是wt?,做完這本書的勞累值就是每天的勞累值之和。

但是辣雞ljh一道題都不會,自然也不知道題目有多難,他只知道題目的難度一定在1~m之間隨機。

他想讓即將參加 NOIP 的你幫他算算katarina大佬做完這本書的勞累值期望

輸入格式

第一行,三個整數\(n,m,k\).

第二行, \(m\)個整數表示 \(wt_1...wt_m\)

輸出格式

輸出勞累值期望對1000000007取模的值。

樣例

樣例輸入1

2 2 2
1 2 

樣例輸出1

750000007

樣例輸入2

5 4 3
2 1 3 5 

樣例輸出2

890625018

資料範圍與提示

樣例1解釋

有{1,1},{1,2},{2,1},{2,2}四種可能,期望為\(\dfrac{7}{4}\)

解題思路

首先這是一道和期望沒關係的期望題,正解比較麻煩 (反正我看不懂),這裡說一種比較通俗易懂的方法:

我們先考慮k天之中的情況,對於k天總方案數顯然是\(m^k\)對於不同的難度不難得出以下的式子:

\(\sum\limits_{i=1}^{m}(i^k-(i-1)^k) \times wt_i\)

最難的是i的方案有\(i^k\)種,但是其中有\((i-1)^k\)種情況是不做貢獻的,因為有\((i-1)^k\)種情況選不到i,以i=2,k=2為例

情況有\((1,2)(2,2)(1,1)(2,1)\)但是有\((1,1)\)這一種情況是不做貢獻的,因此上式正確。

最後我們再給運算結果乘天數就好了。

code



#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=510,mod=1000000007;
int n,m,k,ans,base,day,s[N];
int ksm(int x,int y)
{
	int sum=1;
	while(y)
	{
		if(y&1)
			sum=sum*x%mod;
		y>>=1;
		x=x*x%mod;
	}
	return sum;
}
#undef int
int main()
{
	#define int register long long
	#define ll long long
	scanf("%lld%lld%lld",&n,&m,&k);
	if(k>n)
	{
		printf("0");
		return 0;
	}
	day=n-k+1;
	for(int i=1;i<=m;i++)
		scanf("%lld",&s[i]);
	base=ksm(ksm(m,k),mod-2);
	for(int i=1;i<=m;i++)
	{
		int temp1=ksm(i,k),temp2=ksm(i-1,k);
		ans=(ans+(temp1-temp2+mod)%mod*s[i]%mod+mod)%mod;
	}
	printf("%lld",ans*day%mod*base%mod);
	return 0;
}

T4 寶藏

解題思路

首先正解是狀壓,但是狀壓+DFS好像也能過而且及其容易理解。

首先要確定的一點是我們要DFS的狀態而並非節點,設當前深度為dis[i],f[i]為i狀態最小花費,len[i][j]表示i與j之間道路的距離。不難得出以下式子:

\(f[s|(1<<j-1)]=f[s]+len[i][j] \times dis[j]\)

前提是\(!(s\&(1<<j-1))\)並且\(s\&(1<<i-1)\)因為如果j進去過了就沒有必要再進一遍,而從i向j打通道就一定要滿足i已經被打通。

因為一開始是隨機選,因此我們暴力列舉沒一個節點作為一開始的節點並向下進行深搜

當然上面的這一種打法是有問題的,因為遍歷的深度不同導致了後效性無法處理的問題,我們可以在f陣列上面加上兩維,令 \(f[x][s][dep]\)表示狀態為s並且節點x深度為dep的最小花費。實現過程也與上面的大同小異。

但是我 (懶得) 不屑於碼了,就給出最一開始打法的程式碼吧。。。

code

#include<bits/stdc++.h>
//#define int long long
using namespace std;
const int N=15,M=1e3,base=0x3f3f3f3f;
int n,m,ans=1e9+7,dis[M],len[M+5][M+5],f[1<<N];
void dfs(int s)
{
//	cout<<s<<endl;
	for(int i=1;i<=n;i++)
		if((1<<(i-1))&s)
			for(int j=1;j<=n;j++)
				if(!((1<<(j-1))&s)&&len[i][j]!=0x3f3f3f3f)
//				cout<<i<<' '<<j<<endl;
					if(f[(1<<(j-1))|s]>f[s]+len[i][j]*dis[i])
					{
						int old=dis[j];
						dis[j]=dis[i]+1;
						f[(1<<(j-1))|s]=f[s]+len[i][j]*dis[i];
						dfs((1<<(j-1))|s);
						dis[j]=old;
					}
}
//#undef int
int main()
{
//	#define int register long long
//	#define ll long long
	scanf("%d%d",&n,&m);
	memset(len,0x3f,sizeof(len));
//	cout<<len[0][0]<<endl;
	for(int i=1,x,y,val;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&val);
		len[x][y]=len[y][x]=min(len[x][y],val);
	}
	/*for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				len[i][j]=min(len[i][j],len[i][k]+len[k][j]);*/
	for(int i=1;i<=n;i++)
	{
		memset(dis,0x3f,sizeof(dis));
		memset(f,0x3f,sizeof(f));
		dis[i]=1;
		f[1<<(i-1)]=0;
		dfs(1<<(i-1));
		ans=min(ans,f[(1<<n)-1]);
	}
	printf("%d",ans);
	return 0;
}