1. 程式人生 > 實用技巧 >大假期集訓模擬賽12

大假期集訓模擬賽12

摺紙

題目描述

\(s\) 很喜歡摺紙。

有一天,他得到了一條很長的紙帶,他把它從左向右均勻劃分為 \(N\) 個單位長度,並且在每份的邊界處分別標上數字 \(0\sim n\)

然後小 \(s\) 開始無聊的摺紙,每次他都會選擇一個數字,把紙帶沿這個數字當前所在的位置翻折(假如已經在邊界上了那就相當於什麼都不做)。

\(s\) 想知道 \(M\) 次翻折之後紙帶還有多長。

輸入格式

輸入包含多組資料,第一為一個正整數 \(T,0<T\leq 10\)

接下來,每組資料包括兩行。

第一行包含兩個正整數 \(N\)\(M\) ,表示紙帶的長度和操作的次數。

第二行包含 \(M\)

個整數 \(D_i\) ,其中 \(D_i\) 表示第 \(i\) 次選擇的數字。

輸出格式

每組資料輸出一行,只有一個數字,即紙帶最後的長度。

樣例

樣例輸入

2
5 2
3 5
5 2
3 2

樣例輸出

2
2

資料範圍與提示

\(100\%\) 的資料中 \(N\leq 10^{18},M\leq 3000\)

思路

很簡單的一個模擬,考試的時候想複雜了,而且只是往同一個方向翻折。

其實思路很簡單,我們會發現當我們進行第 \(i\) 次翻折的時候,只有前面的翻折會對這次翻折造成影響,所以我們只需要記錄每次翻折的折點,在遍歷前 \(j(1\leq j<i)\) 次翻折,改變第 \(i\)

次翻折的折點即可。

我們可以通過 \(f[i]\) 來記錄第 \(i\) 次翻折的時候,是向左翻的還是向右翻的,再以 \(a[j]\) 為對稱點,改變 \(a[i]\) 的值即可。

程式碼

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int maxn=1e5+50,INF=0x3f3f3f3f;
inline int read(){
	int x=0,w=1;
	char ch;
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}

int T;
int n,m;
int a[maxn],f[maxn];

signed main(){
	T=read();
	while(T--){
		n=read(),m=read();
		int head=0,tail=n;//記錄當前紙條的首尾
		for(int i=1;i<=m;i++){
			int x=read();
			if(x==head||x==tail)continue;//在邊界直接跳過
			for(int j=1;j<=i-1;j++){//改變x的值
				if(a[j]<x&&f[j]==2){
					x=2*a[j]-x;
				}else if(a[j]>x&&f[j]==1){
					x=2*a[j]-x;
				}
			}
			if(x-head<tail-x){//從左往右翻
				head=x;
				f[i]=1;	
			}else{//從右往左翻
				tail=x;
				f[i]=2;
			}
			a[i]=x;
		}
		printf("%lld\n",tail-head);
	}	
	return 0;
}

water

題目描述

有一塊矩形土地被劃分成 \(n\times m\) 個正方形小塊。這些小塊高低不平,每一小塊都有自己的高度。水流可以由任意一塊地流向周圍四個方向的四塊地中,但是不能直接流入對角相連的小塊中。

一場大雨後,由於地勢高低不同,許多地方都積存了不少降水。給定每個小塊的高度,求每個小塊的積水高度。

注意:假設矩形地外圍無限大且高度為 \(0\)

輸入格式

第一行包含兩個非負整數 \(n,m\)

接下來 \(n\) 行每行 \(m\) 個整數表示第 \(i\) 行第 \(j\) 列的小塊的高度。

輸出格式

輸出 \(n\) 行,每行 \(m\) 個由空格隔開的非負整數,表示每個小塊的積水高度。

樣例

樣例輸入

3 3
4 4 0
2 1 3
3 3 -1

樣例輸出

0 0 0
0 1 0
0 0 1

資料範圍與提示

對於 \(20\%\) 的資料 \(n,m\leq 4\)

對於 \(40\%\) 的資料 \(n,m\leq 15\)

對於 \(60\%\) 的資料 \(n,m\leq 50\)

對於 \(100\%\) 的資料 \(n,m\leq 300\) ,|小塊高度| \(\leq 10^9\)

在每一部分資料中,均有一半資料保證小塊高度非負

思路

前幾天考了好幾次這種題型,將棋盤式的圖轉化成一個一維的圖論來處理。

我們會發現:當一個塊兒的四周有比它高度小或跟它高度相同的塊兒時,它就存不住水了。

我們可以向四周建邊,邊權為兩個塊兒的最大高度,在做一遍最小生成樹,深搜一遍求解即可。

程式碼

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int maxn=300+50,INF=0x3f3f3f3f;
inline int read(){
	int x=0,w=1;
	char ch;
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}

int n,m;
int pan[maxn][maxn];
int dx[4]={1,0,-1,0};
int dy[4]={0,-1,0,1};
int f[maxn*maxn];
int cnt;

struct Edge{
	int from,to,w;
}e[maxn*maxn*10];

struct EDGE{
	int to,next,w;
}g[maxn*maxn*10];

int tot,head[maxn*maxn<<2];
void Add(int u,int v,int w){
	g[++tot].to=v;
	g[tot].w=w;
	g[tot].next=head[u];
	head[u]=tot;
}

bool cmp(Edge a,Edge b){
	return a.w<b.w;
}

int Find(int x){
	return f[x]==x ? x : f[x]=Find(f[x]);
}

void Merge(int x,int y){
	f[Find(x)]=Find(y);
}

void Kurscal(){//最小生成樹
	for(int i=1;i<=cnt;i++){
		int u=e[i].from,v=e[i].to,w=e[i].w;
		if(Find(u)!=Find(v)){
			Merge(u,v);
			Add(u,v,w);//雙向邊
			Add(v,u,w);
		}
	}
}

void DFS(int u,int fa,int maxx){
	for(int i=head[u];i;i=g[i].next){
		int v=g[i].to;
		if(v==fa)continue;
		int vx=v/m+1,vy=v%m;//求出座標值
		if(vy==0){
			vx--;
			vy=m;
		}
		pan[vx][vy]=max(maxx,g[i].w)-pan[vx][vy];//求出積水量
		DFS(v,u,max(maxx,g[i].w));
	}
}

signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			pan[i][j]=read();
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			for(int k=0;k<=3;k++){				
				int xx=i+dx[k],yy=j+dy[k];
				int x=(i-1)*m+j,y=(xx-1)*m+yy;
				if(xx==0||yy==0||yy>m||xx>n){
					y=0;
				}
				if(i==0||j==0||i>n||j>m){
					x=0;
				}
				if(x==0&&y==0)continue;
				e[++cnt].from=x;//雙向邊
				e[cnt].to=y;
				e[cnt].w=max(pan[i][j],pan[xx][yy]);
				e[++cnt].from=y;
				e[cnt].to=x;
				e[cnt].w=max(pan[i][j],pan[xx][yy]);
			}	
		}
	}
	for(int i=1;i<=n*m;i++){
		f[i]=i;
	}
	sort(e+1,e+cnt+1,cmp);
	Kurscal();
	DFS(0,-1,-INF);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			printf("%lld ",pan[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	return 0;
}

找夥伴

題目描述

在班級裡,每個人都想找學習夥伴。夥伴不能隨便找,都是老師固定好的,老師給出要求:夥伴要憑自己實力去找。

老師給每個人發一張紙,上面有數字。假設你的數字是 \(w\) ,那麼如果某位同學手中的數字的所有正約數之和等於 \(w\) ,那麼這位同學就是你的小夥伴。

輸入格式

輸入包含 \(n\) 組資料(最多 \(100\) 組)

對於每組測試資料,輸入只有一個數字 \(w\)

輸出格式

對於每組資料輸出兩行,第一行包含一個整數 \(m\) ,表示有 \(m\) (如果 \(m=0\) ,只輸出一行 \(0\) 即可)個夥伴。

第二行包含相應的 \(m\) 個數,表示夥伴的數字。

注意:小夥伴的數字必須按照升序排列。

樣例

樣例輸入

1
42

樣例輸出

1
1
3
20 26 41

資料範圍與提示

\(100\%\) 的資料,有 \(w\leq 2\times 10^9\)

思路

簡單的題目,簡單的輸入輸出,嗯,數論,嗯,再見!!!

其實這個題就是用了模擬賽10\(T3\) 提到的約數和定理,當時只是求了個個數,這次就是求約數和了,還得記錄一下路徑。


  • 約數和定理的證明:

對於一個大於 \(1\) 的正整數 \(n\) 可以分解質因數:( \(p_i\) 為質數)

\(n=\prod_{i=1}^k p_i^{a_i}={p_1}^{a_1}\times {p_2}^{a_2}\times {p_3}^{a_3}\times ......\times {p_k}^{a_k}\)

\({p_k}^{a_k}\)的約數個數為 \((a_k+1)\)

\(n\) 的約數是從 \({p_1}^{a_1},{p_2}^{a_2},{p_3}^{a_3}......{p_k}^{a_k}\) 中每個選一個相乘得到的。

所以 \(n\) 的約數個數為 \(\prod_{i=1}^k (a_i+1)\)

所以約數和就是每個中選一個相乘求 \(\sum\)

這樣我們就可以化簡為:

\(f[n]=\prod_{i=1}^k (p_i^0+p_i^1+p_i^2+p_i^3+......+p_i^{a_i})\)

就是約數和定理


首先我們將質數都先線性篩篩出來,你也可以打表,除非你想打 \(1e5\) 的表???

然後對 \(w\) 進行拆分,每拆分出一個質數 \(now*=p_i^{a_i}\) ,當 \(w\) 被拆分成 \(1\) 時,此時的 \(now\) 就是一個答案。

程式碼

#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+50,INF=0x3f3f3f3f;
inline int read(){
	int x=0,w=1;
	char ch;
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}

int w;
int prime[maxn+5];
int ans[maxn+5];
int vis[maxn+5];
int tot=0,sum;

void Getprime(){//線性篩
	for(int i=2;i<=maxn;i++){
		if(!vis[i]){
			prime[++tot]=i;	
		}
		for(int j=1;i*prime[j]<=maxn;j++){
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
}

bool Judge(int x){
	for(int i=2;i<=sqrt(x);i++){
		if(x%i==0)return 0;
	}
	return 1;
}

void DFS(int now,int p,int x){
	if(now==1){//被拆分成1
		ans[++sum]=x;
		return;		
	}
	if(Judge(now-1)&&now>prime[p]){//剪枝,如果now-1為質數,now=(now-1)^0+(now-1)^1,直接加上結果
		ans[++sum]=x*(now-1);
	}
	for(int i=p;prime[i]*prime[i]<=now;i++){//列舉質數
		int phi=prime[i];
		int cnt=prime[i]+1;//利用公式
		for(;cnt<=now;phi*=prime[i],cnt+=phi){
			if(now%cnt==0){
				DFS(now/cnt,i+1,x*phi);
			}
		}
	}
}

int main(){
	Getprime();
	while(scanf("%d",&w)==1){
		sum=0;
		DFS(w,1,1);
		sort(ans+1,ans+1+sum);
		printf("%d\n",sum);
		for(int i=1;i<=sum;i++){
			printf("%d ",ans[i]);
		}
		printf("\n");
	}
	return 0;
}

string

題目描述

給定一個由小寫字母組成的字串 \(s\)

\(m\) 次操作,每次操作給定 \(3\) 個引數 \(l,r,x\) 。如果 \(x=1\) ,將 \(s[l]\sim s[r]\) 升序排序;如果 \(x=0\) ,將 \(s[l]\sim s[r]\) 降序排序。你需要求出最終序列。

輸入格式

第一行兩個整數 \(n,m\) ,表示字串長度為 \(n\) ,有 \(m\) 次操作。

第二行一個字串 \(s\)

接下來 \(m\) 行每行三個整數 \(l,r,x\)

輸出格式

一行一個字串表示答案。

樣例

樣例輸入

5 2
cabcd
1 3 1
3 5 0

樣例輸出

abdcc

資料範圍與提示

對於 \(40\%\) 的資料, \(n,m\leq 1000\)

對於 \(100\%\) 的資料, \(n,m\leq 100000\)

思路

  • \(40opts\)的分段

直接用 \(sort\) 就可以了,簡單通俗,分價比較高。


  • \(100opts\)的分段

很明顯的線段樹,但是有那麼一點考驗思維,我們在建樹的時候,若左右兩個端點的字元相同,則父節點就存這個字元的值,若不同,父節點存為 \(0\) ,當我們訪問的時候,若有值,直接輸出 \(r-l+1\) 個本字元。

如下圖:

程式碼

#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+50,INF=0x3f3f3f3f;
inline int read(){
	int x=0,w=1;
	char ch;
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}

int n,m;
char a[maxn];
struct Tree{
	int l,r,w;
}e[maxn<<2];
int cnt[30];

void Build(int rt,int l,int r){//建樹
	e[rt].l=l,e[rt].r=r;
	if(l==r){
		e[rt].w=a[l]-'a'+1;//存值
		return;
	}
	int mid=(l+r)>>1;
	Build(rt<<1,l,mid);//左子樹存值
	Build(rt<<1|1,mid+1,r);//右子樹存值
	if(e[rt<<1].w==e[rt<<1|1].w){//若左右子樹的值相同,父節點存該值
		e[rt].w=e[rt<<1].w;
	}
}

void Init(int rt,int l,int r){//記錄每個字母的數量
	if(e[rt].l>=l&&e[rt].r<=r&&e[rt].w!=0){
		cnt[e[rt].w]+=e[rt].r-e[rt].l+1;
		return;
	}
	if(e[rt].w!=0){
		e[rt<<1].w=e[rt<<1|1].w=e[rt].w;
	}
	int mid=(e[rt].l+e[rt].r)>>1;
	if(l<=mid){
		Init(rt<<1,l,r);
	}
	if(r>mid){
		Init(rt<<1|1,l,r);
	}
} 

void Change(int rt,int l,int r,int w){//修改區間
	if(e[rt].l>=l&&e[rt].r<=r||e[rt].w==w){
		e[rt].w=w;
		return;
	}
	if(e[rt].w!=0){
		e[rt<<1].w=e[rt<<1|1].w=e[rt].w;
		e[rt].w=0;
	}
	int mid=(e[rt].l+e[rt].r)>>1;
	if(l<=mid){
		Change(rt<<1,l,r,w);
	}
	if(r>mid){
		Change(rt<<1|1,l,r,w);
	}
	if(e[rt<<1].w==e[rt<<1|1].w){
		e[rt].w=e[rt<<1].w;
	}
}

void COUT(int rt){//輸出
	if(e[rt].w!=0){
		for(int i=e[rt].l;i<=e[rt].r;i++){
			printf("%c",e[rt].w+'a'-1);
		}
		return;
	}
	COUT(rt<<1);
	COUT(rt<<1|1);
}
int main(){
	n=read(),m=read();
	scanf("%s",a+1);
	Build(1,1,n);
	while(m--){
		int l=read(),r=read(),opt=read();
		memset(cnt,0,sizeof(cnt));
		Init(1,l,r);
		if(opt==1){
			for(int i=1;i<=26;i++){//正序排序
				if(cnt[i]!=0){
					Change(1,l,l+cnt[i]-1,i);
					l+=cnt[i];
				}
			}
		}else{
			for(int i=26;i>=1;i--){//降序排序
				if(cnt[i]!=0){
					Change(1,l,l+cnt[i]-1,i);
					l+=cnt[i];
				}
			}
		}
	}
	COUT(1);
	return 0;
}