本場鏈接:Codeforces Round #695 (Div. 2)
A. Wizard of Orz
題目大意
有\(n\)個燈,每個燈一開始都是0,每一秒鍾每個燈顯示的數字會往上一位,但是由於每個燈只能顯示單個數位,所以從\(9\)增大時得到的是\(0\).在任意時刻,你可以指定唯一一個燈被停止,下一秒后,他相鄰的兩個燈也會進入暫停狀態.問,在合理的設置一個燈被停止之后,所有燈停止,顯示的最大的數是多少.
注:先增加數位,后停止.
數據范圍:
\(1 \leq n \leq 2*10^5\)
思路
顯然應該是從高位到低位盡量最大,一個很直接但是不對的想法是,直接讓最高位就是9,后一位是8,接着往后遞推,依次減少.不對的原因是如果讓第二位是8的時候被停止,可以使前三位是\(989\),總體更大.后面遞推即可.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
int cur = 9;
printf("9");
if(n >= 2) printf("8");
forn(i,3,n)
{
printf("%d",cur);
if(cur == 9) cur = 0;
else ++cur;
}
puts("");
}
return 0;
}
B. Hills And Valleys
題目大意
對於任意一個\(j \in [2,n-1]\)來說如果他滿足\(a_j\)嚴格小於身邊兩個元素,或者\(a_j\)嚴格大於身邊兩個元素,分別稱為波谷和波峰.令一個數組\(a\)的牛逼值是整個數組中波谷和波峰的數量之和,現在你可以修改數組中任意一個元素,使他變成任意一個值,問這個數組最小的牛逼值是多少.
數據范圍:
\(1 \leq n \leq 3 *10^5\)
\(1 \leq a_i \leq 10^9\)
思路
首先肯定統計一下原來數組的波谷波峰的個數,隨便搗鼓一下就算完了,記為\(sum\).
考慮如何做這個修改:這是一個非常顯然的套路,因為只能修改一個數,所以直接枚舉每一個數被修改的情況即可,進而可以想到,加入要修改\(a_i\)那么最好的修改策略就是讓\(a_i = a_{i-1}\)或者\(a_i = a_{i + 1}\)因為這樣簡單又能消滅波峰和波谷,之后我們直接枚舉每一位被修改的情況就可以了.
首先從\(sum\)里面挖掉與\(i\)相關的,構成三元組的部分,其次把\(a_i\)修改,統計新的三元組的貢獻,枚舉獲得最小值即可.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 3e5+7;
int up[N],dw[N],a[N],n;
int calc(int i)
{
if(i <= 1 || i >= n) return 0;
int res = 0;
if(a[i] > a[i - 1] && a[i] > a[i + 1]) ++res;
if(a[i] < a[i - 1] && a[i] < a[i + 1]) ++res;
return res;
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
forn(i,1,n) scanf("%d",&a[i]),up[i] = 0,dw[i] = 0;
up[0] = dw[0] = up[n + 1] = dw[n + 1] = 0;
int res = 1e9,sum = 0;
forn(i,2,n - 1) sum += calc(i);
res = sum;
forn(i,1,n)
{
int tmp = sum - calc(i) - calc(i - 1) - calc(i + 1);
int p = a[i];
a[i] = a[i - 1];
tmp += calc(i) + calc(i - 1) + calc(i + 1);
a[i] = p;
res = min(res,tmp);
tmp = sum - calc(i) - calc(i - 1) - calc(i + 1);
a[i] = a[i + 1];
tmp += calc(i) + calc(i - 1) + calc(i + 1);
a[i] = p;
res = min(res,tmp);
}
printf("%d\n",res);
}
return 0;
}
C. Three Bags
題目大意
有三個可重集,分別給定這三個集合的大小和里面的元素.現在定義一種操作:分別指定兩個非空集合A\B
,從A
里面取出一個元素\(a\),從B
里面取出一個元素\(b\),把\(b\)從B
里面刪除,把A
集合中的\(a\)替換成\(a-b\).經過若干次操作之后,所有集合里面的元素個數會只剩下一個,求這個剩下的數的最大值.
數據范圍:
\(1 \leq n1,n2,n3 \leq 3 * 10^5\)
\(1 \leq n1 + n2 + n3 \leq 3 * 10^5\)
\(1 \leq a \leq 10^9\)
思路
稍微有點抽象的一題,最好模擬一下過程.
通過模擬過程我們可以發現這個題想要最大的值加和進去,應該先讓一個集合里的最小值去減掉外面一個集合的值,再把需要合並的數拿去減這個最小值減外面的值剩下的結果,這樣的話,這個合並的結果就是原來的數加上外面的集合的一個值並且減去另外一個集合里面的最小值.用形式化的語言來說明,設三個集合\(A,B,C\),三個元素對應\(a,b,c\).假設我們現在想合並這三個數到\(a\)頭上,並且\(c\)是比\(b\)小的,那么可以先讓\(c-b\),之后讓\(a-(c-b)\),這樣的話我們就讓\(b\)加到\(a\)頭上了,但是需要支付\(-c\)的代價,那么什么時候\(a\)最大呢,顯然應該是\(c\)取最小值的時候.但是這里有個問題,對於和\(a\)元素在同一個集合里的元素如何統計?我們可以發現對於同一個集合的元素只要外面存在元素,那么一定可以讓外面的直接減掉再加回到原來的集合里去,這里我們直接認為整個\(A\)集合是合並好的就可以了(因為初始情況不會有集合是空的).
想到這里大概的就可以明確一下題目做法了:枚舉每個集合作為被合並的集合,對於被合並的集合自己來說,他自己集合里的所有元素是可以直接合並的,對於其他兩個集合的元素來說,他們需要一個最小值來作為跳板才能進入\(A\)集合,現在來考慮這個跳板的問題:經過前面的分析不難想到跳板其實有兩種情況,一種是\(B\)和\(C\)里面都有一個最小值作為跳板,那么他們兩個集合中只要不是最小值的元素(如果有多的,那么就認為只有其中一個被拿來作為跳板,其他的都可以拿去合並)都可以直接去合並,合並之后需要支付的代價就是兩個最小值;還有一種就是我只有一個跳板,例如所有的\(B\)及和里面的元素都通過\(C\)集合合並到\(A\),這種情況下\(C\)集合里沒有任何一個元素是跳了一次再進入\(A\)的,所以代價就是整個\(C\)集合的負數和.
綜上,剩下的就是枚舉每個集合以及有兩個跳板和一個跳板的情況了.最終找出最大值即可.
不過需要注意一下,我的代碼思路就是先求和,求和的時候要先挖掉本來的貢獻,再去支付代價,比賽時就是這里沒考慮清楚沒算到結果.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 3e5+7;
int n[3];
int a[3][N];
ll sum[3],minv[3];
int main()
{
scanf("%d%d%d",&n[0],&n[1],&n[2]);
forn(i,0,2) forn(j,1,n[i]) scanf("%d",&a[i][j]);
forn(i,0,2)
{
minv[i] = a[i][1];
forn(j,1,n[i])
{
sum[i] += a[i][j];
minv[i] = min(minv[i],1ll*a[i][j]);
}
}
ll res = 0,all = sum[0] + sum[1] + sum[2];
// cout << all << endl;
// cout << minv[0] << " " << minv[1] << " " << minv[2] << endl;
res = max(res,all - 2 * minv[0] - 2 * minv[1]);
res = max(res,all - 2 * minv[0] - 2 * minv[2]);
res = max(res,all - 2 * minv[1] - 2 * minv[2]);
res = max(res,all - 2 * sum[0]);
res = max(res,all - 2 * sum[1]);
res = max(res,all - 2 * sum[2]);
printf("%lld",res);
return 0;
}
D. Sum of Paths
題目大意
有一個\(n\)個格子的橫直條,一開始有個機器人,位置隨意.每一秒他可以向左或者向右走一格,但是不可以跨過邊界.任何一個長度是\(k\)的路徑,只要沒有走出格,就認為是合法的一種路線.對於每個格子都有一個權值,當機器人走到上面的時候,得分就會增加對應的權值.問所有合法的路徑得到的分數的總和是多少,對\(10^9+7\)取模.
注意:權值可以多次計算.只要經過了就算.
例子:3個格子可以走4步,那么(1,2,3,4) \ (1,2,1,2)都算是合法的路線.
接下來有\(q\)個詢問,每個詢問給定兩個值\(i,x\)表示將\(a_i\)修改成\(x\).每個詢問之后輸出修改后的總和.
數據范圍:
\(2 \leq n \leq 5000\)
\(1 \leq k \leq 5000\)
$1 \leq q \leq 2 * 10^5 $
思路
看到單點修改的詢問就可以想到這肯定是一個套路的維護,考慮每個點上的\(a_i\)對整個和的貢獻.
設\(cnt_i\)表示第\(i\)個格子在所有合法的路線中出現了幾次,那么顯然答案就是\(\sum_{i=1}^ncnt_i*a_i\),只要我們能求出\(cnt_i\)對於后面的每個詢問,只需要把一開始的貢獻刪掉,再加上修改之后新的貢獻就可以了.
剩下的問題就是如何求\(cnt_i\),經過模擬找規律之后可以發現大概是很難找規律的,因為他會有很多往回走的情況,不過題目的數據范圍啟發了\(dp\)的思路,一個比較套路的但是還不知道又什么用的\(dp\)做法是:\(f[i][j]\)表示當前在\(i\)這個位置,已經走了\(j\)步的合法方案個數.狀態轉移方程比較簡單這里就不展開了\(f[i][j] = f[i - 1][j - 1] + f[i + 1][j - 1]\)在\(i=1\)和\(i = n\)的情況下特判.這個信息暫時還想不到跟\(cnt_i\)有什么直接的聯系,先放放.
接下來我們考慮能不能從\(cnt_i\)身上找點信息逆推回去,因為\(cnt_i\)是表示所有合法的路徑中\(i\)編號出現的個數,那么我們可以通過走了多少步來划分\(cnt_i\)的來源,也就是說我們去看所有合法的長度是\(k\)的路徑,枚舉\(j\in[0,k]\),第\(j\)位是\(i\)的方案的個數,把這些方案累加起來就是出現\(i\)的次數.這里另設一個狀態\(g[i][j]\):
- \(g[i][j]\)表示在所有的合法路徑中編號\(i\)在出現在\(j\)位的次數.
- 顯然根據上面的划分來說\(cnt_i = \sum_{i=0}^k g[i][j]\).
- 入口:初始全為\(0\)即可
- 轉移:整個長度為\(k\)的路徑,第\(j\)為出現的是\(i\),那么前面\(j\)步的方案數恰好就是走了\(j\)步走到\(i\)的方案數即\(f[i][j]\),對於后面\(k-j\)步,我們可以從反方向考慮,某個狀態走了\(k-j\)步走到了\(i\),也就是\(f[i][k - j]\),那么轉移方程就是\(g[i][j] = f[i][j] * f[i][k - j]\)
做完\(g\)的遞推之后,求一下\(cnt\)即可.剩下的詢問就非常簡單了,不做多過多解釋.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 5005,MOD = 1e9+7;
ll f[N][N],cnt[N],g[N][N];
int a[N];
int main()
{
int n,k,q;scanf("%d%d%d",&n,&k,&q);
forn(i,1,n) scanf("%d",&a[i]);
forn(i,1,n) f[i][0] = 1;
forn(j,1,k) forn(i,1,n)
f[i][j] = (i >= 2 ? f[i - 1][j - 1] : 0) + (i <= n - 1 ? f[i + 1][j - 1] : 0),f[i][j] %= MOD;
forn(i,1,n) forn(j,0,k) g[i][j] = (g[i][j] + f[i][j] * f[i][k - j] % MOD) % MOD;
forn(i,1,n) forn(j,0,k) cnt[i] = (cnt[i] + g[i][j]) % MOD;
// forn(i,1,n) printf("%lld ",cnt[i]);puts("");
ll ori = 0;
forn(i,1,n) ori = (ori + cnt[i] * a[i] % MOD) % MOD;
while(q--)
{
int i,x;scanf("%d%d",&i,&x);
ori = (ori - cnt[i] * a[i]) % MOD;
ori = (ori + MOD) % MOD;
a[i] = x;
ori = (ori + cnt[i] * a[i] % MOD) % MOD;
printf("%lld\n",ori);
}
return 0;
}
E. Distinctive Roots in a Tree
題目大意
給定一個\(n\)個點的樹,定義一個牛逼的點當且僅當從這個點走出去的任意一條路徑(包括起點自己)中都不存在相同的權值的點對.求樹中牛逼的點的個數.
數據范圍:
\(1 \leq n \leq 2 * 10^5\)
\(1 \leq a_i \leq 10^9\)
思路
首先比較顯然的是需要統計權值的個數,一開始要么離散化一下要么扔map統計.總之預處理出一個\(cnt\)表示權值為\(i\)的點數.
預處理完了之后考慮這個題本身應該怎么做:考慮某個點\(u\)以及他的兒子\(c\).記\(a_i\)為點\(i\)的顏色,那么如果以\(c\)為根的子樹里存在某個點與\(a_u\)相同,那么說明所有可能是牛逼點的點應該屬於以\(c\)為根的子樹的集合里,因為對於其他的點來說,一定會有一條路走\(u\)再走\(c\)里面的某個點,除非你把這兩個相同顏色的點切開,使得兩個點在各一邊才行.
如果下面分析看不明白,建議具體化一下圖,以及看一下后面的總述
那么這就是一種情況:假如\(u\)的某個兒子里存在一個點與之同色,那么所有牛逼的點應該屬於這個兒子的子樹集合.當然這里並不明確是誰,這個不重要,因為這個過程是一個往下遞歸的過程,我們之后再來考慮下面的同色的點的情況.這里應該對整顆子樹\(c\)打一個標記表示牛逼點可能是他們,還要對所有其他的點打一個標記表示牛逼的點絕對不是他們了.這里的處理方式也有多種,我的是對整顆樹打一個\(+1\)的標記,對子樹打一個\(-1\)的標記,只有標記值為\(0\)的點表示是一個牛逼的點.
當然討論到這里肯定還沒完,因為如果我們只有上面這種對整顆樹打正一,對需要修改的子樹打負一的標記的話,從\(c\)往下遞歸,走到那個與\(u\)同色的點的時候,我們並不會"向上看",但是實際上我們要把這個同色點(記作\(z\))以下的部分也切斷,盡管他下面可能沒有同色的點了,但是因為外面存在的\(u\)的原因,對於\(z\)以下的點往上走一定會有一條\(z->c->u\)的路線導致同色.所以這里還需要討論一種情況就是,如果統計完了某顆子樹\(u\),但是在\(u\)之外的點中還存在與\(u\)相同的顏色的點,那么\(u\)為根的子樹也必須要拋棄掉(即打一個正一標記).
總的來說,這個過程就是看\(u\)下的兒子\(c\)里的子樹是否存在一個\(z\),且\(a_u=a_z\),如果存在的話,只有\(c\)到\(z\)這部分的點是被夾住的,通過分析我們可以知道只有被夾住的點才不會出現相同顏色的路徑.那么打標記的意圖就是使的只有被夾住的部分標記是\(0\),先對所有點打一個\(+1\),其次只有點\(c\)以下的子樹打\(-1\),此時點\(c\)以下的子樹所有點都是\(0\).我們應該對點\(z\)也打上一個標記\(1\),但是我們並不知道他具體在哪里,所以我們先向下遞歸,走到每個點的時候"向上看",看上面之外的地方是否存在同色的點,如果存在就把這顆子樹拋棄掉(打標記\(1\)).
接下來考慮怎么維護,要么樹上差分,要么就用\(dfs\)序差分.這里就不展開了.就是一個子樹標記的問題.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 2e5+7,M = 2 * N,LIM = 18;
int edge[M],succ[M],ver[N],idx;
int depth[N],fa[N][LIM + 3];
int a[N],cnt[N],dwcnt[N],sum[N];
int dfn[N],time_stamp,siz[N],n;
vector<int> val;
inline int link(int x)
{
return lower_bound(val.begin(),val.end(),x) - val.begin();
}
void cover(int l,int r,int v)
{
if(l > r) return ;
sum[l] += v;
sum[r + 1] -= v;
}
void add(int u,int v)
{
edge[idx] = v;
succ[idx] = ver[u];
ver[u] = idx++;
}
void dfs(int u,int fa)
{
dfn[u] = ++time_stamp; siz[u] = 1;
int allu = dwcnt[a[u]];
++dwcnt[a[u]];
for(int i = ver[u];~i;i = succ[i])
{
int v = edge[i];
if(v == fa) continue;
int other = dwcnt[a[u]];
dfs(v,u);
if(dwcnt[a[u]] > other)
{
cover(1,n,1);
cover(dfn[v],dfn[v] + siz[v] - 1,-1);
}
siz[u] += siz[v];
}
allu = dwcnt[a[u]] - allu;
if(allu < cnt[a[u]]) cover(dfn[u],dfn[u] + siz[u] - 1,1);
}
int main()
{
memset(ver,-1,sizeof ver);
scanf("%d",&n);
forn(i,1,n) scanf("%d",&a[i]),val.push_back(a[i]);
sort(val.begin(),val.end());
val.erase(unique(val.begin(),val.end()),val.end());
forn(i,1,n) a[i] = link(a[i]),++cnt[a[i]];
forn(i,1,n - 1)
{
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs(1,-1);
forn(i,1,n) sum[i] += sum[i - 1];
int res = 0;
forn(i,1,n) res += (sum[i] == 0);
printf("%d",res);
return 0;
}