1. 程式人生 > 實用技巧 >NOIP2017 模擬賽

NOIP2017 模擬賽

不知道教練哪裡找來的一套老題,雖然題目比較的陳舊,但是還是非常與水平的一套題,廢話不多說。

$\color{blue} \text {斜率}$

$\color{blue} \text {【題目描述】}$

給定平面上 n 個點的座標,求所有經過這些點中至少兩個點的直線的最大斜率。

$\color{blue} \text {【輸入】}$

第一行一個整數 n,表示點的個數。

接下來 n 行,每行兩個正整數 x,y,描述每個點的座標。

$\color{blue} \text {【輸出】}$

一行一個實數表示答案,保留小數點後 3 位。

$\color{blue} \text {【輸入樣例】}$

3
1 2
2 3
3 4

$\color{blue} \text {【輸出樣例】}$

1.000

$\color{blue} \text {【提示】}$

【樣例 2】

見選手目錄下 slope.in/slope.ans下載

【資料範圍與約定】

對於 20%的資料,n≤10

對於 50%的資料,n≤10^3

對於 100%的資料,n≤5×10^5,座標,沒有兩點橫座標相同。

$\color{blue} \text {【解題思路】}$

這道題其實是一道結論題,當時考試的時候我也不知道為什麼自己就推這個結論推了這麼舊,但是最後好在還是A了他,並且成功的繞開了他的坑點,這道題的結論就是我們斜率的最大值只可能是在我們對於每一個點進行x軸排序然後找任意兩個相隔最近的兩個點之間來產生這個斜率的最大值,如果不信的話可以自己去畫一個圖推導一下。

下面說一下本題目的坑點,就是說他的斜率有可能是一個負的,所以說我們初始化答案ans變數一定是把他初始成一個負數,否則會GG,我的運氣比較好,我只把他初始到-1然後就卡過去了,大家一定要初始到更小的值去。

AC code:

#include<bits/stdc++.h>
#define gc() getchar()//caution!!!
#define N 500005
using namespace std;
/*inline char gc() {
  static char buf[1<<18],*fs,*ft;
  return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<18,stdin)),fs==ft)?EOF:*fs++;
}*/
template<class T>
inline void read(T &aa) {
  register int k=0,f=1;
  register char c=gc();
  for (;!isdigit(c);c=gc()) if(c=='-')f=-1;
  for (;isdigit(c);c=gc()) k=(k<<3)+(k<<1)+(c-'0');
  aa=k*f;
}
template<class T>
inline void out(T x){if(x>9)out(x/10);putchar(x%10+'0');}
struct sd{
	int x,y;
}p[N];
int n;double ans=-1;
double slope(int x1,int y1,int x2,int y2){
	return (double)(y2-y1)/(double)(x2-x1);
}
void check1(){
	for(int i=1;i<=n;++i){
		for(int j=i+1;j<=n;++j){
			double now=slope(p[i].x,p[i].y,p[j].x,p[j].y);
			ans=max(ans,now);
		}
	}
	printf("%.3lf",ans);
}
bool cmp(sd a,sd b){if(a.x<b.x) return true; return false; }
int main()
{
//    freopen("slope.in", "r", stdin);freopen("slope.out", "w", stdout);
	scanf("%d",&n);for(int i=1;i<=n;++i){scanf("%d%d",&p[i].x,&p[i].y);}
	sort(p+1,p+1+n,cmp);
	/*for(int i=1;i<=n;++i){
		printf("%d %d\n",p[i].x,p[i].y);
	}*/
	if(n<=1000){check1();return 0;}
	for(int i=1;i<n;++i){
		double now=slope(p[i].x,p[i].y,p[i+1].x,p[i+1].y);
		ans=max(now,ans);
	}
	printf("%.3lf",ans);
//    fclose(stdin);fclose(stdout);
    return 0;
}
/*
3
1 2
2 3
3 4
*/

$\color{blue} \text {最優路線}$

$\color{blue} \text {【題目描述】}$

一個 n 個點 m 條邊的無重邊無自環的無向圖,點有點權,邊有邊權,定義一條路徑的權值為路徑經過的點權的最大值乘邊權最大值。求任意兩點間的權值最小的路徑的權值。

$\color{blue} \text {【輸入】}$

第一行兩個整數 n,m,分別表示無向圖的點數和邊數。

第二行 n 個正整數,第 i 個正整數表示點 i 的點權。

接下來 m 行每行三個正整數 ui,vi,wi,分別描述一條邊的兩個端點和邊權。

$\color{blue} \text {【輸出】}$

n 行每行 n 個整數,第 i 行第 j 個整數表示從 i 到 j 的路徑的最小權值,如果從 i 不能到達 j,則該值為−1。特別地,當 i=j 時輸出 0。

$\color{blue} \text {【輸入樣例】}$

3 3
2 3 3
1 2 2
2 3 3
1 3 1

$\color{blue} \text {【輸出樣例】}$

0 6 3
6 0 6
3 6 0

$\color{blue} \text {【提示】}$

【樣例 2】

見選手目錄下 path.in/path.ans下載。

【資料範圍與約定】

對於 20%的資料,n≤5,m≤8。

對於 50%的資料,n≤50

對於 100%的資料,n≤500,m≤n×(n−1)/2,邊權和點權不超過 10^9。

$\color{blue} \text {【解題思路】}$

這道題應該算是本套題中最難的一道題了,思路也並不是那麼的好想,但是可以肯定的一點就是這道題大家一看就應該知道是一道DP,具體實現方法就是弗洛伊德演算法的改裝版,我們定義一個叫做dis[][]陣列的東西作為我們的轉移變數,分別儲存的是從i號點到j號點的最小的最大路徑長度乘上路徑上的最大點權,我們對點權進行從小到大的排序,那麼我們就可以保證我們每一次加進去的點(作為我們Floyd演算法的中轉點,都是最優秀的,當然我們對於該條路徑上的最大值一定還是要和該路徑上兩邊的點的點權進行比較,我們可以保證中間的點一定是比現在的中轉點的點權要小一些的,因為我們在構建這條路徑的時候一定是先把這些點加進去了,而我們是從小到大排的序!)相信大家應該都懂了,具體一些操作的實現可以見程式碼,程式碼還是寫的比較清楚的!

AC code:

/*
    Name: path
    Copyright: LPA
    Author: Mudrobot
    Date: 2018/10/17 16:26:01
    Description: Dynamic Programming + Floyd
*/
#include<bits/stdc++.h>
#define gc() getchar()//caution!!!
#define LL long long
#define N 505
using namespace std;
/*inline char gc() {
  static char buf[1<<18],*fs,*ft;
  return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<18,stdin)),fs==ft)?EOF:*fs++;
}*/
template<class T>
inline void read(T &aa) {
  register int k=0,f=1;
  register char c=gc();
  for (;!isdigit(c);c=gc()) if(c=='-')f=-1;
  for (;isdigit(c);c=gc()) k=(k<<3)+(k<<1)+(c-'0');
  aa=k*f;
}
template<class T>
inline void out(T x){if(x>9)out(x/10);putchar(x%10+'0');}
struct sd{LL val,loc;}poi[N];
LL n,m,mp[N][N],val[N],dis[N][N];
bool cmp(sd a,sd b){if(a.val<b.val) return true;return false;}
void clear(){
	memset(mp,63,sizeof(mp));memset(dis,63,sizeof(dis));
}
int main()
{
    //freopen("path.in", "r", stdin);freopen("path.out", "w", stdout);
	read(n);read(m);clear();LL a,b,c;
	for(int i=1;i<=n;++i) read(poi[i].val),val[i]=poi[i].val,poi[i].loc=i,mp[i][i]=0;
	for(int i=1;i<=m;++i){
		read(a);read(b);read(c);
		mp[a][b]=mp[b][a]=c;
		dis[b][a]=dis[a][b]=min(dis[a][b],mp[a][b]*max(val[a],val[b]));
	}
	sort(poi+1,poi+1+n,cmp);
	for(int u=1;u<=n;++u){
		LL k=poi[u].loc;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=n;++j){
				if(mp[i][j]>max(mp[i][k],mp[k][j])){
					mp[i][j]=max(mp[i][k],mp[k][j]);
					dis[i][j]=min(dis[i][j],max(max(val[i],val[j]),val[k])*mp[i][j]);
				}
			}
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			if(i==j) printf("0 ");
			else if(dis[i][j]==dis[0][0])printf("-1 ");
			else printf("%lld ",dis[i][j]);
		}
		printf("\n");
	}
    //fclose(stdin);fclose(stdout);
    return 0;
}
/*
3 3
2 3 3
1 2 2
2 3 3
1 3 1
*/

$\color{blue} \text {小 G 的線段樹}$

$\color{blue} \text {【題目描述】}$

小 G 是一名 OIer,他最近學習了一種高階資料結構——線段樹,

做題時,他遇到了如下的問題:

維護一個序列,要求支援三種操作:

1.區間加上一個數 x
2.區間賦值為一個數 x
3.求一個區間的和

小 G 是一個愛思考的同學。他在做出來了這題之後,又提出了一個新的問題:如果把所有的操作隨機打亂,那麼每個詢問的期望輸出是多少呢?注意,隨機打亂既所有 m!種操作排列的出現概率均等。

為了方便,我們假設詢問在最後且不參與隨機打亂。

考慮到精度問題,只要你的答案和標程答案的相對誤差不超過10^−8 就算正確。

$\color{blue} \text {【輸入】}$

第一行兩個整數 nmq,分別表示序列長度、修改數和詢問數

接下來一行 n 個整數 ai,表示序列的初始值

接下來 m 行,每行 4 個整數 c,l,r,x
若 c=1,則表示把區間[l,r]的元素加上 x
若 c=2,則表示把區間[l,r]的元素全賦為 x
接下來 q 行,每行 2 個整數 l,r,代表每次詢問的左右端點。

$\color{blue} \text {【輸出】}$

q 行,每行一個實數,按照輸入順序分別為 q 個詢問的期望答案

答案保留 3 位小數

$\color{blue} \text {【輸入樣例】}$

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

$\color{blue} \text {【輸出樣例】}$

5.000
3.167
3.500
1.500
4.000
11.667
12.167
17.167

$\color{blue} \text {【提示】}$

【樣例 2】

見選手目錄下 segment.in/segment.ans下載

【資料範圍與約定】

對於 30%的資料,n≤10,m≤10
對於 60%的資料,n≤1000,m≤1000
對於另外 10%的資料,沒有操作 1
對於另外 10%的資料,q=1
對於 100%的資料,n,m,q≤100000,1≤ai≤100000,1 操作中的x≤100,2 操作中的 x≤100000。

$\color{blue} \text {【解題思路】}$

這道題千萬不要被題目所忽悠,並不是一道線段樹的題目,只是一道數學題加上差分的優化,在這裡主要講一下數學的思路,差分大家可以自己腦補或者看我的AC code,數學思路主要是這樣,千萬不要想的太複雜,我們首先先把所有重置操作抽取出來,想象他們十一塊一塊的隔板,那麼如果有k個重置操作那麼就有k+1個空格,然後我們就把所有的區間加操作隨機的加入進去,但是我們發現只有把這些操作插入到最後一個隔板後面時才能對最後的結果造成影響,所以說我們關於所有區間加法的期望就非常好算了,對於每一個點就是,在他上面進行的加法的和乘上1/(k+1)即可,然後我們呢在考慮一下對於區間重置對答案所作的貢獻,就是所有區間貢獻的總和乘上1/k因為每一種都有1/k的概率成為最後一塊隔板,然後把這兩個期望相加就是我們那個點上的答案,然後這道題就結束了,最主要的就是要考慮當前點如果沒有區間重置操作時的情況,非常容易錯,然後這道題講到這裡相信大家都能AC了:

AC code:

/*
    Name: Segment
    Copyright: njc
    Author: Mudrobot
    Date: 2018/10/17 15:28:03
    Description: Mathmatics
*/
#include<bits/stdc++.h>
#define gc() getchar()//caution!!!
#define N 100005
#define LL long long
using namespace std;
/*inline char gc() {
  static char buf[1<<18],*fs,*ft;
  return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<18,stdin)),fs==ft)?EOF:*fs++;
}*/
template<class T>
inline void read(T &aa) {
  register int k=0,f=1;
  register char c=gc();
  for (;!isdigit(c);c=gc()) if(c=='-')f=-1;
  for (;isdigit(c);c=gc()) k=(k<<3)+(k<<1)+(c-'0');
  aa=k*f;
}
template<class T>
inline void out(T x){if(x>9)out(x/10);putchar(x%10+'0');}
LL n,m,q,ini[N],add[N],sset[N],cnt[N];
double sum[N];
int main()
{
    //freopen(".in", "r", stdin);freopen(".out", "w", stdout);
	scanf("%lld%lld%lld",&n,&m,&q);
	for(int i=1;i<=n;++i) scanf("%lld",&ini[i]);
	LL a,b,c,d;
	for(int i=1;i<=m;++i){
		scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
		if(a==1){
			add[b]+=d;add[c+1]-=d;
		}
		else{
			sset[b]+=d;sset[c+1]-=d;cnt[b]++;cnt[c+1]--;
		}
	}
	a=0;b=0;c=0;
	for(int i=1;i<=n;++i){
		a+=add[i];b+=sset[i];c+=cnt[i];
		if(!c){
			sum[i]=(double)ini[i]+(double)a;
		}
		else{
			sum[i]=(double)b/c+(double)a/(c+1);
		}
	}
	for(int i=1;i<=n;++i)sum[i]+=sum[i-1];
	for(int i=1;i<=q;++i){
		scanf("%lld%lld",&a,&b);
		printf("%.3lf\n",sum[b]-sum[a-1]);
	}
    //fclose(stdin);fclose(stdout);
    return 0;
}
/*
5 4 8
2 3 3 3 3
1 1 3 2
1 3 5 1
2 2 4 1
2 1 3 4
1 1
2 2
3 3
4 4
5 5
1 3
2 5
1 5
*/

By njc