1. 程式人生 > 實用技巧 >10.23 考試總結

10.23 考試總結

noip​ 模擬賽 省選模擬賽。

\(wdnmd\),上次考試標題就是省選模擬賽,然後就爆玲 GG 了。

這次不會又要爆玲了吧,心肺停止。

預計得分 : 100+100+50 = 250.

實際得分: 100 +100+50 = 250

一分沒掛。實際難度好像和 \(noip\) 差不多。

\(emmm\),考試標題原來就是來唬人的。

T1 rank

Desprition

\(n\) 支隊伍,隊伍從 \(1\)\(n\) 開始編號,第 \(i\) 只隊伍有兩個數值: \(w_i,v_i\) \(w_i\) 表示每隻隊伍的初始氣球的數量,

\(v_i\) 表示當這支隊伍的氣球數一旦超過 \(v_i\) 則他們所有的氣球就會爆炸,即氣球數量會歸零。

現在比賽已經結束,\(A\) 君在第一隻隊伍,他可以把自己的氣球送給其他的隊伍,即使會讓那個隊伍的氣球爆炸。

\(A\) 君可以將手裡任意數量的氣球送給任意的隊伍,現在 \(B\) 君想知道,\(A\) 君送完氣球之後,他們隊伍的排名最優(數值最小)是多少。

輸入格式

第一行一個整數, \(n\) 表示隊伍的數量,接下來 \(n\) 行每行兩個整數表示 \(w_i,v_i\) 表示隊伍擁有的氣球數,和最多能擁

有的氣球數。

資料範圍:

對於 25% 的資料 \(n,v_,w_i\leq 50\)

對於 50% 的資料 \(n,v_i,w_i\leq 5000\)

另有 25% 的資料 \(n\leq 20\)

100% 的資料 \(n\leq 3\times 10^5,0\leq w_i\leq v_i\leq 10^{18}\)

50pts 暴力瞎搞就行。

正解

貪心加堆。

顯然 \(A\) 君 把自己的氣球送給氣球數比自己多的人,來使其他人的氣球爆炸,才會使他的排名儘可能的靠前。

至於氣球數比他小的,我們暫時就先需要考慮他。

我們肯定優先選的 \(v_i-w_i\) 較小的人來使他氣球爆炸,因為這樣會讓 \(A\) 君 剩下的儘可能的多,同時排名也會往上升,即讓答案變的更優。

假如說你不這麼做的話,遇到 \(v_i = 100000,w_i = 10\) 的資料,你一直傻乎乎的送給這支隊伍氣球,然後你的氣球都用完了,他還沒有爆炸,

然後你就 \(GG\) 了。

所以我們可以用堆來模擬這一個過程,一開始先把所有氣球數比 \(A\) 君多的隊伍都放進一個小根堆中,以 \(v_i-w_i\), 為關鍵字排序。

然後每次取出堆頂,讓 \(A\) 君送給他氣球使他爆炸 (這個 A君是個狼人)。

之後不要忘記 \(A\) 君送完之後自己的氣球數也會變小,這時候也需要把之前沒加進堆的,但比 $A $ 君送完之後的氣球數大的隊伍放進堆中。

最後的答案·就是每個時刻堆的大小的最小值 \(+1\).

程式碼挺好寫的,只不過要注意一下邊界問題。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define int long long
const int N = 3e5+10;
int n,x,v,ans,last;
struct node
{
	int w,v,val;
}e[N];
priority_queue<int,vector<int>, greater<int> > q;
bool comp(node a,node b)
{
	if(a.w == b.w) return a.val < b.val;
	else return a.w > b.w;
}
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
signed main()
{
	freopen("rank.in","r",stdin);
	freopen("rank.out","w",stdout);
	n = read(); x = read(); v = read();
	for(int i = 1; i <= n-1; i++)
	{
		e[i].w = read();
		e[i].v = read();
		e[i].val = e[i].v - e[i].w;
	}
	e[n].w = 0; e[n].val = 0;
	sort(e+1,e+n+1,comp);
	for(int i = 1; i <= n; i++)
	{
		if(e[i].w <= x){ last = i; break; }//先把初始氣球數量比A大的都放入一個小根堆中
		else q.push(e[i].val);
	}
	ans = (int) q.size() + 1;
	while(!q.empty())
	{
		int t = q.top()+1; q.pop();//取出堆頂,使堆頂的那隻隊伍氣球爆炸
		if(x - t < 0) break;//如果A剩下的氣球不足以使堆頂的那隻隊伍氣球爆炸,那麼A君無論在怎麼操作,自己的排名都不會往上升,這時候直接 break就可以
		x -= t;//x即為A君剩下的氣球數
		for(int i = last; i <= n; i++)
		{
			if(e[i].w <= x || i == n) { last = i; break;}//把之前沒有放進堆的,但現在卻比x大的放進堆中
			else q.push(e[i].val);
		} 
		ans = min(ans,(int) q.size() + 1);
	}
	printf("%lld\n",ans);
	fclose(stdin); fclose(stdout);
	return 0;
}

T2 task

desprition

\(A\) 君接到了一個任務,要求他在 \(C\) 國按順序到達 \(K\) 個城市,任務中一個城市可能會多次到達。

\(C\) 國共有 \(n\) 個城市,並通過 \(n-1\) 條雙向道路聯通,即 \(C\) 國的城市道路形成了一個樹形結構。

\(C\) 國某些道路設有單向收費站,即對於道路 \((u,v)\) 收費站會對 從 $u $ 行駛到 \(v\) 的司機收費,一個道路最多有一個

通行方向收費,收費站第一次收費 \(1\) 元,但司機每經過一次這個收費站,就會導致價格翻倍,即第二次收費 \(2\)

元,第 \(3\) 次收費 \(4\) 元,以此類推。

\(A\) 君問你他至少要帶多少錢上路,\(A\) 君初始在城市 \(1\) ,請你輸出答案對 \(1e9+7\) 取模的結果。

輸入格式

第一行一個整數 \(n\) 表示 \(C\) 國城市個數。

接下來 \(n-1\) 行每行三個整數表示有一條連線 \(u,v\) 的道路,若 \(x=0\) 表示這條道路不收費。

\(x=1\) 則表示從 \(v-u\) 的通行方向收費。

\(n+1\) 行一個整數 \(k\) 表示任務中的城市個數。

最後一行 \(k\) 個整數表示按順序依次經過的城市。

資料範圍

20% 的資料 \(n,k\leq 100\)

另有 30% 的資料 \(C\) 國的道路形成一條鏈。

100% 的資料 \(n\leq 10^5,1\leq k\leq 10^6\)

20pts 對於每個點直接暴力 \(bfs\) 一下統計答案就行。

30pts 鏈 不會寫沒寫過。

solution

不得不說這題是真的坑,輸入中 \(u,v\) 竟然表示的是從 \(v-u\) 的路徑收費。這也太不符合常理了吧。

還有就是 \(A\) 的路徑其實是 \(1-a_1-a_2-a_3....a_n\) 然後機房中好多大佬原本打了正解卻沒看到這一點直接 \(WA\) 了。

看到樹上路徑問題,我們考慮差分一下。

我們設 \(up[x]\) 表示從 \(x-fa[x]\) 這條邊被經過的次數。

\(down[x]\) 表示從 \(fa[x]-x\) 這條邊被經過的次數。

然後假設說我們當前要從 \(u-v\),我們把路徑拆成兩部分考慮。

首先,從 \(u-lca(u,v)\) 這一部分是上行的,所以要對路徑上的點的 \(up\) 陣列加 1

相反 \(lca(u,v)-v\) 的路徑是下行的,要對 \(down\) 陣列加 \(1\)

然後直接差分一波就做完了。

注意最後 \(dfs\) 統計答案的時候要考慮 \(x-fa[x]\) 對答案的貢獻,即 反邊對答案的貢獻。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int N = 1e5+10;
const int p = 1e9+7;
int n,m,u,v,w,num,ans,tot = 1;
int dep[N],siz[N],son[N],head[N],fa[N],top[N],up[N],down[N],base[1000010];
struct node
{
	int to,net,w;
}e[N<<1];
void add(int x,int y,int w)
{
	e[++tot].to = y;
	e[tot].w = w;
	e[tot].net = head[x];
	head[x] = tot;
}
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
void get_tree(int x)
{
	dep[x] = dep[fa[x]] + 1; siz[x] = 1;
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if(to == fa[x]) continue;
		fa[to] = x;
		get_tree(to);
		siz[x] += siz[to];
		if(siz[to] > siz[son[x]]) son[x] = to;
	}
}
void dfs(int x,int topp)
{
	top[x] = topp;
	if(son[x]) dfs(son[x],topp);
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if(to == fa[x] || to == son[x]) continue;
		dfs(to,to);
	}
}
int lca(int x,int y)
{
	while(top[x] != top[y])
	{
		if(dep[top[x]] < dep[top[y]]) swap(x,y);
		x = fa[top[x]];
	}
	return dep[x] <= dep[y] ? x : y;
}
void get_ans(int x,int fa)
{
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if(to == fa) continue;
		get_ans(to,x);
		up[x] += up[to];
		down[x] += down[to];
		if(e[i].w == 1) ans = (ans + (base[down[to]] - 1 + p) % p) % p;//fa[x]-x 這條邊的貢獻
		else if(e[i ^ 1].w == 1) ans = (ans + (base[up[to]] - 1 + p) % p) % p; //x-fa[x] 這條邊的貢獻
	}
//	cout<<x<<" "<<up[x]<<" "<<down[x]<<endl;
}
signed main()
{
	freopen("task.in","r",stdin);
	freopen("task.out","w",stdout);
	n = read(); base[0] = 1;
	for(int i = 1; i <= n-1; i++)
	{
		u = read(); v = read(); w = read();
		if(w == 0) add(u,v,w), add(v,u,w);
		else add(u,v,0), add(v,u,1);
	}
	get_tree(1); dfs(1,1);
	num = read(); u = 1;
	for(int i = 1; i <= num; i++) base[i] = (base[i-1] * 2) % p;
	for(int i = 1; i <= num; i++)
	{
		v = read();
		int Lca = lca(u,v);
		up[u]++; up[Lca]--;//差分一下
		down[v]++; down[Lca]--;
		u = v;
	}
	get_ans(1,1);
	printf("%lld\n",ans);
	fclose(stdin); fclose(stdout);
	return 0;
}

T3 tree

給你一顆樹,問你刪除樹上一條邊之後,剩下的兩棵子樹中直徑的較大值之和。

50% 的資料 \(n\leq 2000\) , 100% 的資料 \(n\leq 10^5\)


感覺和 去年 \(csp\) Day2T3 那道題差不多,然鵝我還是不會做。

50pts

直接暴力列舉斷那條邊,然後求剩下兩棵子樹的直徑就可以。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N = 1e5+10;
int n,tot,ans,l,r,w,res;
int head[N],f[N],g[N];
struct query
{
	int u,v;
}q[N];
struct node
{
	int to,net,w;
}e[N<<1];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
void add(int x,int y,int w)
{
	e[++tot].to = y;
	e[tot].w = w;
	e[tot].net = head[x];
	head[x] = tot;
}
bool check(int x,int y)
{
	if((x == l && y == r) || (x == r && y == l)) return 1;
	return 0;
}
void dfs(int x,int fa)
{
//	cout<<x<<" "<<fa<<" "<<l<<" "<<r<<endl;
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if(to == fa || check(x,to)) continue;
		dfs(to,x);
		if(f[to] + e[i].w > f[x])
		{
			g[x] = f[x];
			f[x] = f[to] + e[i].w;
		}
		else if(f[to] + e[i].w > g[x])
		{
			g[x] = f[to] + e[i].w;
		}
	}
	res = max(res,f[x] + g[x]);
}
int slove(int x)
{
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	res = 0;
	dfs(x,x);
	return res;
}
signed main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	n = read();
	for(int i = 1; i <= n-1; i++)
	{
		q[i].u = read(); q[i].v = read(); w = read();
		add(q[i].u,q[i].v,w); add(q[i].v,q[i].u,w);
	}
	for(int i = 1; i <= n-1; i++)
	{
		memset(f,0,sizeof(f));
		l = q[i].u; r = q[i].v;		
		ans += max(slove(l), slove(r));
	}
	printf("%lld\n",ans);
	fclose(stdin); fclose(stdout);
	return 0;
}

solution

不會寫QAQ。