集訓模擬賽10

Vocanda發表於2020-07-07

前言

又發現了許多需要學習的東西……

NO.1 BZOJ 4281 LCA(不知道叫什麼名字 )

Description

給定一棵有\(n\)個點的無根樹,相鄰的點之間的距離為\(1\),一開始你位於\(m\)點。之後你將依次收到\(k\)個指令,每個指令包含兩個整數\(d\)\(t\),你需要沿著最短路在\(t\)步之內(包含\(t\)步)走到\(d\)點,如果不能走到,則停在最後到達的那個點。請在每個指令之後輸出你所在的位置。

Input

第一行包含三個正整數\(n,m,k(1\le m\le n\le 1000000,1\le k\le 1000000)\)
接下來\(n-1\)行,每行包含兩個正整數\(x,y(1\le x,y\le n)\),描述一條樹邊。
接下來\(k\)行,每行兩個整數\(d\),\(t\)\((1\le d\le n,0\le t\le 10^9)\),描述一條指令。

Output

輸出一行,包含\(k\)個正整數,即執行每條指令後你所在的位置。

樣例

樣例輸入

3 1 2
1 2
2 3
3 4
1 1

樣例輸出

3 2

分析

這個題原本的題目是\(LCA\),考試的時候就變成了”不知道叫什麼名字 “,然後就……,其實就是求\(LCA\)的板子,就是有一些卡常,加一下快讀快寫就行。
我們每一次求出\(d\)和上一次位置的\(LCA\),然後找出兩個點之間的距離,如果大於\(t\),那麼分成兩種情況:
1.上一次位置到最近公共祖先的距離大於\(t\),我們就可以直接從\(d\)向上走\(t\)個。
2.上一次位置到最近公共祖先距離小於\(t\),那麼就是總距離減去\(t\),然後從\(d\)向上走減完的距離。

程式碼



#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
int f[maxn][23],dis[maxn],deep[maxn],head[maxn];
struct Node{
	int v,next;
}e[maxn<<1];
int tot,n,m,d,t,k;
inline int read(){//快讀
      int s=0,f=1; 
      char ch=getchar(); 
      while(ch<'0'||ch>'9'){ 
            if(ch=='-') f=-1; 
            ch=getchar(); 
      }
      while(ch>='0'&&ch<='9'){ 
            s=s*10+ch-'0';
            ch=getchar(); 
      } 
      return s*f; 
}
void Add(int x,int y){//建邊
	e[++tot].v = y;
	e[tot].next = head[x];
	head[x] = tot;
}
void dfs(int now,int fa){//求出每個點的深度
    f[now][0]=fa;
    deep[now]=deep[fa]+1;
    for(int i=1;(1<<i)<=deep[now];i++){
        f[now][i]=f[f[now][i-1]][i-1];
    }
    for(int i=head[now];i;i=e[i].next){
        if(fa!=e[i].v) dfs(e[i].v,now);
    }
}
int lca(int x,int y){//倍增求lca
    int ans=0;
    if(deep[x]>deep[y]) swap(x,y);
    int len=deep[y]-deep[x],k=0;
    while(len){
    	if(len & 1){
            y=f[y][k];
        }
    	++k;
    	len>>=1;
    }
    if(x==y) return x;
    for(int i=20;i>=0;i--){
        if(f[x][i]==f[y][i]) continue;
        x=f[x][i];
        y=f[y][i];
    }
    return f[x][0];
}
int js(int x,int d){//計算向上走完能走的距離之後的位置
	for(int i=20;~i;i--){
		if((1<<i)<=d){
			x=f[x][i];
			d-=(1<<i);
		}
	}
	return x;
}
void write(int x){//快寫
    if(x<0){
    	putchar('-');
		x=-x;
	}
    if(x>9) 
		write(x/10);
    putchar(x%10+'0');
}
int main(){
	n=read();
	m=read();
	k=read();
	int pre = m;
	for(int i=1;i<n;++i){
		int x=read();
		int y=read();
		Add(x,y);
		Add(y,x);
	}
	dfs(1,1);
	for(int i=1;i<=k;++i){
		d=read();
		t=read();
		int z=lca(d,pre);
		if(deep[pre]-deep[z]>=t)pre = js(pre,t);//從上個位置到lca的距離大於t
		else if(deep[d]+deep[pre]-2*deep[z]<=t)pre = d;//能到達d點
		else {
			t=deep[d] + deep[pre]-2*deep[z]-t;//上個位置到lca距離小於t但是到不了d
			pre = js(d,t);
		}
		write(pre);
		printf(" ");
	}
	return 0;
}

NO.2 蟲洞

題目描述

\(N\)個蟲洞,M條單向躍遷路徑。從一個蟲洞沿躍遷路徑到另一個蟲洞需要消耗一定量的燃料和\(1\)單位時間。蟲洞有白洞和黑洞之分。設一條躍遷路徑兩端的蟲洞質量差為\(delta\)

從白洞躍遷到黑洞,消耗的燃料值減少\(delta\),若該條路徑消耗的燃料值變為負數的話,取為\(0\)

從黑洞躍遷到白洞,消耗的燃料值增加\(delta\)

路徑兩端均為黑洞或白洞,消耗的燃料值不變化。

作為壓軸題,自然不會是如此簡單的最短路問題,所以每過\(1\)單位時間黑洞變為白洞,白洞變為黑洞。在飛行過程中,可以選擇在一個蟲洞停留\(1\)個單位時間,如果當前為白洞,則不消耗燃料,否則消耗\(s_i\) 的燃料。現在請你求出從蟲洞\(1\)\(N\)最少的燃料消耗,保證一定存在\(1\)\(N\)的路線。

輸入格式

\(1\)行:\(2\)個正整數\(N,M\)
\(2\)行:\(N\)個整數,第\(i\)個為\(0\)表示蟲洞\(i\)開始時為白洞,\(1\)表示黑洞。
\(3\)行:\(N\)個整數,第\(i\)個數表示蟲洞\(i\)的質量\(w_i\)
第4行:\(N\)個整數,第\(i\)個數表示在蟲洞i停留消耗的燃料\(s_i\)
\(5..M+4\)行:每行\(3\)個整數,\(u,v,k\),表示在沒有影響的情況下,從蟲洞\(u\)到蟲洞\(v\)需要消耗燃料\(k\)

輸出格式

一個整數,表示最少的燃料消耗。

樣例

樣例輸入

4 5
1 0 1 0
10 10 100 10
5 20 15 10
1 2 30
2 3 40
1 3 20
1 4 200
3 4 200

樣例輸出

130

樣例解釋

按照\(1\to 3\to 4\)的路線。

資料範圍與提示

對於\(30\%\)的資料: \(1\le N\le 100,1\le M\le 500\)
對於\(60\%\)的資料: \(1\le N\le 1000,1\le M\le 5000\)
對於\(100\%\)的資料: \(1\le N\le 5000,1\le M\le 30000\)
其中\(20\%\)的資料為\(1\le N\le 3000\)的鏈
\(1\le u,v\le N, 1\le k,w_i,s_i\le 200\)

分析

因為黑白洞之間可以相互轉化,所以我們用分層圖處理,因為每一次走過之後都會白變黑,黑變白,所以如果是同種,就應該從黑到白,從白到黑建一個邊,邊權為輸入的邊權。因為可以等待,所以把每個點從白到黑連邊,邊權為\(0\),從黑到白也一樣,但是邊權應該是\(s_i\)。我們還需要建兩邊黑白不同的邊,按照要求建邊就行了,還是因為每一次走過之後都會白變黑,黑變白,所以雖然是兩邊不同,建邊的時候還是要統一顏色建邊,然後跑最短路就行了。

值得注意的是:如果開始是黑洞,那麼應該從第二層開始最短路。

程式碼



#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e4+10;
int head[maxn];
struct Node{
	int v,next,val;
}e[maxn*10];
int tot;
int dis[maxn],vis[maxn],s[maxn],w[maxn],jl[maxn];
void Add(int x,int y,int val){//建邊
	e[++tot].v = y;
	e[tot].next = head[x];
	head[x] = tot;
	e[tot].val = val;
}
priority_queue<pair<int,int> >q;
int n,m;
void Dij(int x){//最短路
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[x] = 0;
	q.push(make_pair(0,x));
	while(!q.empty()){
		int y = q.top().second;
		q.pop();
		if(vis[y])continue;
		vis[y] = 1;
		for(int i=head[y];i;i=e[i].next){
			int v=e[i].v;
			if(dis[v]>dis[y]+e[i].val){
				dis[v] = dis[y]+e[i].val;
				q.push(make_pair(-dis[v],v));
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		scanf("%d",&jl[i]);
	}
	for(int i=1;i<=n;++i){
		scanf("%d",&w[i]);
	}
	for(int i=1;i<=n;++i){
		scanf("%d",&s[i]);
	}
	for(int i=1;i<=m;++i){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		if(jl[x] == jl[y]){//兩邊是同一個顏色,因為會變,所以應該到不同顏色建邊
			Add(x,y+n,z);//加n後是黑色
			Add(x+n,y,z);
		}
		else {//兩邊顏色不同,簡便要相同,按要求處理邊權即可
			int val = abs(w[x]-w[y]);
			Add(x+n,y+n,z+val);
			Add(x,y,max(z-val,0));
		}
	}
	for(int i=1;i<=n;++i){//等待的時候簡便
		Add(i,i+n,0);
		Add(i+n,i,s[i]);
	}
	if(jl[1] == 1){//開始是黑洞要從黑洞那一層開始
		Dij(n+1);
	}
	else Dij(1);
	cout<<min(dis[n],dis[2*n])<<endl;//取最小值
}

NO.3 圖騰計數

題目描述

\(whitecloth\) 最近參觀了樓蘭圖騰。圖騰的所在地有一排\(N\)個柱子,\(N\)個柱子的高度恰好為一個\(1\)\(N\)的排列,而樓蘭圖騰就隱藏在這些柱子中。

由於\(whitecloth\)弱爆了,他只知道圖騰由\(3\)個柱子組成,這三個柱子組成了下凸或上凸的圖形(>.<),所謂下凸,設三個柱子的高度從左到右依次為 \(h_1,h_2,h_3\),那麼\(h_1>h_2,h_3>h_2\),上凸則滿足\(h_1<h_2,h_3<h_2\)

現在\(whitecloth\)也找不到圖騰具體是哪三個柱子,他只想知道滿足這兩個形狀的柱子有幾組。

輸入格式

第一行一個數\(?\),接下來一行\(?\)個數,依次表示每個柱子的高度

輸出格式

一行兩個數,表示下凸形狀的數量和上凸形狀的數量,用空格隔開

樣例

樣例輸入

5
1 5 3 2 4

樣例輸出

3 4

資料範圍與提示

\(?\le 200000\)

分析

與昨天的題超級像,就是求逆序對,因為我歸併還沒調過,所以先分析線段樹。
其實很簡單,我們輸入的時候儲存一下每個點的座標,然後先升序排序,線段樹依次尋找當前左邊和右邊的比當前小的數量,然後記錄下來。接著降序,,找到左邊和右邊比現在大的數量並記錄。然後最後統計答案。

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
#define ll long long
struct Node{
	int l,r,sum;
	ll mn,mx;
}t[maxn<<2];
struct N{
	int wz;
	ll hi;
}a[maxn];
ll sumlmin[maxn],sumrmin[maxn],sumlmax[maxn],sumrmax[maxn];
bool cmpxiao(N a,N b){
	return a.hi<b.hi;
}
bool cmpda(N a,N b){
	return a.hi>b.hi;
}
void pushup(int rt){
	t[rt].mn = t[rt<<1].mn+t[rt<<1|1].mn;
	t[rt].mx = t[rt<<1].mx+t[rt<<1|1].mx;
}
void Build(int rt,int l,int r){
	t[rt].l = l;
	t[rt].r = r;
	if(l == r){
		t[rt].mx = t[rt].mn = 0;
		return;
	}
	int mid = (l+r)>>1;
	Build(rt<<1,l,mid);
	Build(rt<<1|1,mid+1,r);
	pushup(rt);
}
void changemin(int rt,int pos,ll w){
	if(t[rt].l == t[rt].r){
		t[rt].mn+=w;
		return;
	}
	int mid = (t[rt].l + t[rt].r)>>1;
	if(pos<=mid)changemin(rt<<1,pos,w);
	else changemin(rt<<1|1,pos,w);
	pushup(rt);
}
void changemax(int rt,int pos,ll w){
	if(t[rt].l == t[rt].r){
		t[rt].mx+=w;
		return;
	}
	int mid = (t[rt].l + t[rt].r)>>1;
	if(pos<=mid)changemax(rt<<1,pos,w);
	else changemax(rt<<1|1,pos,w);
	pushup(rt);
}
ll checkmin(int rt,int l,int r){
	if(t[rt].l >= l && t[rt].r <= r){
		return t[rt].mn;
	}
	ll ans=0;
	int mid = (t[rt].l+t[rt].r)>>1;
	if(l<=mid)ans+=checkmin(rt<<1,l,r);
	if(r>mid)ans+=checkmin(rt<<1|1,l,r);
	return ans;
}
ll checkmax(int rt,int l,int r){
	if(t[rt].l >= l && t[rt].r <= r){
		return t[rt].mx;
	}
	ll ans=0;
	int mid = (t[rt].l+t[rt].r)>>1;
	if(l<=mid)ans+=checkmax(rt<<1,l,r);
	if(r>mid)ans+=checkmax(rt<<1|1,l,r);
	return ans;
}
//上邊都是線段樹板子
int n;
int main(){
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>a[i].hi;
		a[i].wz = i;
	}
	Build(1,1,n);
	sort(a+1,a+n+1,cmpxiao);//升序排序
	for(int i=1;i<=n;++i){//查詢每個點左右比他矮的
		sumlmin[a[i].wz] = checkmin(1,1,a[i].wz);	
		sumrmin[a[i].wz] = checkmin(1,a[i].wz,n);
		changemin(1,a[i].wz,1);
	}
	sort(a+1,a+n+1,cmpda);//降序排序
	for(int i=1;i<=n;++i){//查詢每個點左右比他高的
		sumlmax[a[i].wz] = checkmax(1,1,a[i].wz);
		sumrmax[a[i].wz] = checkmax(1,a[i].wz,n);
		changemax(1,a[i].wz,1);
	}
	ll ansmin=0,ansmax=0;
	for(int i=1;i<=n;++i){
		ansmin += sumlmin[a[i].wz]*sumrmin[a[i].wz];//左右的比當前小的乘起來
		ansmax += sumlmax[a[i].wz]*sumrmax[a[i].wz];//同上
	}
	cout<<ansmax<<" "<<ansmin<<"\n";
	return 0;
}

NO.4 十字繡

題目描述

考古學家發現了一塊布,布上做有針線活,叫做“十字繡”,即交替地在布的兩面穿線。

布是一個\(n\times m\)的網格,線只能在網格的頂點處才能從布的一面穿到另一面。每一段線都覆蓋一個單位網格的兩條對角線之一,而在繡的過程中,一針中連續的兩段線必須分處布的兩面。並且每一段線只能走一次。

給出布兩面的圖案,問最少需要幾針才能繡出來?一針是指標不離開布的一次繡花過程。

輸入格式

\(1\)行兩個數\(N\)\(M\)
接下來\(N\)行每行\(M\)個數描述正面。
再接下來\(N\)行每行\(M\)個數描述反面。
每個格子用.(表示空),/(表示從右上角連到左下角),\(表示從左上角連到右下角)和X(表示連兩條對角線)表示。
輸出格式

一個數,最少要用的針數。

樣例

樣例輸入

4 5
.....
....
....
.....
.....
....\
.\X..
.....

樣例輸出

4

(為使輸入資料更直觀,樣例為圖片格式,下面是文字格式的樣例)

資料範圍與提示

對於\(100\%\)的資料,\(1\le n,m\le 200\)

分析

剛一看到這個題,第一個想法就是建好圖,然後\(Tarjan\)求聯通分量,但是這麼做肯定是不對的。

我們分析一下,如果一個聯通塊裡有兩個正面的線,一個反面的線,那麼我們能夠推出來這個聯通塊需要兩針才行。在建圖的時候,我們把這個字元組成的一個個格子都變成一個點,每個點的標號不同,然後建邊

從這個結論可以判斷,每一個聯通塊裡的點都有其相對應的最小針數,而這個最小針數就是正面的線和反面的線數量差的絕對值,因為每個邊的兩個點都會計算,所以最後統計出來的答案應該除以\(2\)

應該注意的一點就是最後如果統計出來的答案是\(0\),我們可以認為是環狀,那麼應該是有一針的,這裡不用除以\(2\),所以直接讓\(ans\)\(2\)即可

最後需要注意的就是'\'這個符號,直接打出來肯定是不行的,我們可以把它直接放到最後直接\(else\),也可以'\ \',還有用\(ASCII\)碼,值為92來判斷。

程式碼



#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
struct Node{
	int v,next;
}e[maxn<<4];
bool vis[maxn],b[maxn];
char s[205][205];
int n,m,a[550][550],z[maxn<<1],num;
int ans1,ans2,tot,head[maxn<<1];
void Add(int x,int y,int data){//建邊,如果正面就讓data為0,這個點的度++,反之則--,我們就在這裡求出了答案所需要的絕對值
	if(data == 0)z[x]++;
	else z[x]--;
	e[++tot].v = y;
	e[tot].next = head[x];
	head[x] = tot;
}
void Dfs(int x){//深搜找每個聯通塊的針數
	vis[x] = 1;
	ans2+= abs(z[x]);
	for(int i=head[x];i;i=e[i].next){
		if(!vis[e[i].v])Dfs(e[i].v);
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n+2;++i){
		for(int j=1;j<=m+2;++j){
			a[i][j] = ++num;
		}
	}
	for(int i=1;i<=n;++i){//正面的所有邊
		cin>>s[i]+1;
		for(int j=1;j<=m;++j){//建邊的時候正面的data也就是邊權為0,便於判斷
			if(s[i][j] == '\\'){//建邊
				Add(a[i][j],a[i+1][j+1],0);
				Add(a[i+1][j+1],a[i][j],0);
				b[a[i][j]] = b[a[i+1][j+1]] = 1;//標記這裡有線
			}
			if(s[i][j] == '/'){
				Add(a[i][j+1],a[i+1][j],0);
		    	        Add(a[i+1][j],a[i][j+1],0);
		   		b[a[i][j+1]]=b[a[i+1][j]]=1;//同上
			}
			if(s[i][j]=='X'){//X則對角建邊
				Add(a[i][j+1],a[i+1][j],0);
				Add(a[i][j],a[i+1][j+1],0);
				Add(a[i+1][j],a[i][j+1],0);
				Add(a[i+1][j+1],a[i][j],0);
				b[a[i][j]]=b[a[i+1][j]]=b[a[i+1][j+1]]=b[a[i][j+1]]=1;
			}
		}
	}
	for(int i=1;i<=n;++i){//反面的建邊,以下的細節同上
		cin>>s[i]+1;
		for(int j=1;j<=m;++j){
			if(s[i][j] == '\\'){
				Add(a[i][j],a[i+1][j+1],1);
				Add(a[i+1][j+1],a[i][j],1);
				b[a[i][j]] = b[a[i+1][j+1]] = 1;
			}
			if(s[i][j] == '/'){
				Add(a[i][j+1],a[i+1][j],1);
		    	Add(a[i+1][j],a[i][j+1],1);
		   		b[a[i][j+1]]=b[a[i+1][j]]=1;
			}
			if(s[i][j]=='X'){
				Add(a[i][j+1],a[i+1][j],1);
				Add(a[i][j],a[i+1][j+1],1);
				Add(a[i+1][j],a[i][j+1],1);
				Add(a[i+1][j+1],a[i][j],1);
				b[a[i][j]]=b[a[i+1][j]]=b[a[i+1][j+1]]=b[a[i][j+1]]=1;
			}
		}
	}
	
	for(int i=1;i<=num;++i){
		if(b[i] && !vis[i]){//當前點沒有訪問過且有線
			ans2=0;
			Dfs(i);//深搜求聯通塊的答案
			if(!ans2)ans1+=2;//聯通塊答案為0,也就是環,因為一會要除以2,所以直接加上2
			else ans1 += ans2;
		}
	}
	cout<<ans1/2<<"\n";
	
}

相關文章