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。