本場鏈接:Codeforces Round #703 (Div. 2)
閑話
感覺CF就沒打的好的日子,總是差那么一點就做完了,蠻破壞心情的.CD都是差一點就拐過彎來了.先把A~D寫了,E還在施工,下午可能還有事估計得丟晚上再更新E.
A. Shifting Stacks
有\(n\)堆書,每堆書的高度是\(h_i\).你可以從\(i\)位置抽若干本書堆到\(i+1\)列上.操作次數不限,問你能不能把所有書的高度順次做成一個嚴格上升序列.
數據范圍:
\(1 \leq n \leq 100\)
思路
數據范圍很小,而且限制比較強:你只能從\(i\)移動到\(i+1\).所以不妨這樣暴力地把所有的書往后面推:第一摞書如果不是\(0\)就把所有書全部挪到第二堆上,第二堆書在不少於第一堆書的前提下全部往第三堆書上挪,看她最后是不是一個嚴格上升序列就可以了.
代碼
#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 = 105;
ll a[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
forn(i,1,n) scanf("%lld",&a[i]);
bool ok = 1;
if(a[1] != 0)
{
a[2] += a[1];
a[1] = 0;
}
forn(i,2,n)
{
if(a[i] <= a[i - 1]) ok = 0;
a[i + 1] += a[i] - a[i - 1] - 1;
a[i] = a[i - 1] + 1;
}
if(!ok) puts("NO");
else puts("YES");
}
return 0;
}
B. Eastern Exhibition
把城市地圖看做是一個二維平面,有\(n\)個居民住在\((x_i,y_i)\)位置上.現在要建立一個博物館,要求任何人到博物館的距離\(dist = |x-tx| + |y - ty|\)相同,其中\((tx,ty)\)表示博物館的位置.
數據范圍:
\(1 \leq n \leq 1000\)
\(0 \leq x_i,y_i \leq 10^9\)
思路
二維問題分割成兩個一維問題:在\(x\)軸上有多少個位置選定之后,保證所有人的\(x\)坐標到此絕對值都相同且最小,這個就是貨倉選址那個題的經典結論:取所有\(x\)的中位數,如果\(n\)是奇數那么中位數唯一;反之可以選中間兩個數之間的任何一個位置.那么由於兩個子問題相互獨立,直接乘法原理統計方案數就可以了.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
typedef pair<int,int> pii;
#define x first
#define y second
const int N = 1005;
int x[N],y[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
forn(i,1,n) scanf("%d%d",&x[i],&y[i]);
if(n & 1) puts("1");
else
{
sort(x + 1,x + n + 1);
sort(y + 1,y + n + 1);
int cx = x[n / 2 + 1] - x[n / 2],cy = y[n / 2 + 1] - y[n / 2];
printf("%lld\n",(1ll * cx + 1) * (cy + 1));
}
}
return 0;
}
C. Guessing the Greatest
交互
給定一個\(n\)有一個長度為\(n\)的數組.每次可以發起一個詢問\([l,r]\),返回在\([l,r]\)之內次大值的下標.至多詢問40/20次,要求得出最大值的下標.
數據范圍:
\(2 \leq n \leq 10^5\)
所有元素不同
思路
詢問\(20\)次,很近\(\log_2^n\).考慮構造一個二分框架:\([l,r]\)區間,以及一個中點\(mid\),如果發起對\([l,mid]\)的詢問,是否能正確的移動指針.
很快會發現直接去搞一個二分沒什么信息.但是可以這么先來一次:詢問出\([1,n]\)里次大值的位置\(p\).接着可以想到一個方向:假如\([1,p]\)的詢問結果是\(p\)的話,那么最大值一定在\([1,p]\)這個區間內.不過有個例外情況:\(p=1\)時是不可能的,需要排除.
如果最大值在\([1,p]\)這個區間內,那么現在的問題可以轉換成:找最后一個位置\(k\)滿足\([k,p]=p\),這個二分就可以了.
反過來如果最大值在\([p,n]\)這個區間內,那么同樣可以轉換成:找第一個位置\(k\)滿足\([p,k]=p\),同樣跑一個二分.
這里其實還有一種想法:把\(p\)確定之后每次踢掉另外一半的數組,然后去看剩下那部分數組,構成一個范圍更小的子問題.但是不能保證查詢次數.這樣做二分是肯定的.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
typedef pair<int,int> pii;
#define x first
#define y second
map<pii,int> cache;
int query(int l,int r)
{
if(l >= r) return -1;
pii q = {l,r};
if(cache.count(q)) return cache[q];
cout << "? " << l << " " << r << endl;
int res;cin >> res;
return res;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
int n;cin >> n;
int l = 1,r = n;
cout << "? " << "1 " << n << endl;
int p;cin >> p;
if(p > 1 && query(1,p) == p)
{
int l = 1,r = p;
while(l < r)
{
int mid = l + r + 1 >> 1;
if(query(mid,p) == p) l = mid;
else r = mid - 1;
}
cout << "! " << l << endl;
}
else
{
int l = p,r = n;
while(l < r)
{
int mid = l + r >> 1;
if(query(p,mid) == p) r = mid;
else l = mid + 1;
}
cout << "! " << l << endl;
}
return 0;
}
D. Max Median
給定一個長度為\(n\)的數組\(a\),求一個長度至少為\(k\)的連續子段,要求中位數最大值.
在本題中,中位數只取\((n+1)/2(↓)\).
數據范圍:
\(1 \leq k \leq n \leq 2 * 10^5\)
\(1 \leq a_i \leq n\)
思路
首先貪心肯定死,考慮dp.但是dp必須要有一個性質:對於以\(i\)為末尾的段,中位數最大只在長度固定的段取到.但是着很顯然不顯示.那么只剩一種做法了:直接枚舉所有可能的答案,並且檢查答案是否合法.枚舉部分可以考慮使用二分加速,也是一個比較套路的手段了:二分是合理的原因在於對於一個合理的答案\(x\),即中位數至少能是\(x\)的前提下,如果把\(x\)變小也是成立的,這個性質具有單調性.
直接構造一個二分就可以了,問題剩下一個如何檢查中位數是否至少能是\(x\).問題可以轉換成判定:是否存在一個長度至少是\(k\)的連續子段,滿足\(\geq x\)的數的個數嚴格大於\(<x\)的數的個數.那么這個就是一個比較套路的東西了,每次可以構造一個數組\(s[i] = a[i] >= x ? 1 : -1\).並且做一個前綴和.如果嚴格大於,那么就說明和嚴格大於\(0\).問題就轉換成了與\(0\)的大小關系.形式來說:找一個\(i\geq k\)以及一個\(j \in [0,i-k]\).滿足\(s[i] - s[j] > 0\).那么貪心\(s[j]\)只用前面的最小值,維護前綴最小值就可以判斷了.
代碼
#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;
int a[N],s[N],minv[N];
int n,k;
bool check(int x)
{
forn(i,1,n) s[i] = s[i - 1] + (a[i] >= x ? 1 : -1);
forn(i,1,n) minv[i] = min(minv[i - 1],s[i]);
forn(i,k,n) if(s[i] - minv[i - k] > 0) return 1;
return 0;
}
int main()
{
scanf("%d%d",&n,&k);
forn(i,1,n) scanf("%d",&a[i]);
int l = 1,r = n;
while(l < r)
{
int mid = l + r + 1 >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
printf("%d",l);
return 0;
}
E. Paired Payment
給定一個\(n\)點\(m\)邊無向帶邊權圖.每次移動至少移動兩條邊,例如有\(a-b-c\)則一次移動是直接從\(a->c\),產生的代價是\((w_{ab} + w_{bc})^2\).求\(1\)到所有點的最短距離,如果不能走到輸出-1
.
數據范圍:
\(1 \leq n \leq 10^5\)
\(1 \leq m \leq 2 * 10^5\)
\(1 \leq w_i \leq 50\)
思路
最暴力的做法就是直接從\(u\)搜所有點\(v\),再從\(v\)搜所有點\(z\)跑單源最短路.考慮如何優化:對於任何一個點\(v\)如果以他作為中轉點,也就是\(u-v-z\)這樣走,那么從\(v\)走進來的所有邊,可以把所有非最小值的踢掉.因為不是最小值的走到\(z\)不如用最小值的走過去,對所有的點維護一個入邊的最小值就可以了.
這個玩意能跑過去的原因是權值的范圍非常小,最多不過引發50次額外的更新.也不知道寫點什么,就挺神奇的.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
typedef pair<int,int> pii;
#define x first
#define y second
const int N = 1e5+7,M = 4 * N;
int edge[M],succ[M],ver[N],cost[M],idx;
int dist[N],mn[N];
bool st[N];
void add(int u,int v,int w)
{
edge[idx] = v;
cost[idx] = w;
succ[idx] = ver[u];
ver[u] = idx++;
}
void dijkstra()
{
priority_queue<pii,vector<pii>,greater<pii>> pq;
memset(dist,0x3f,sizeof dist);
memset(mn,0x3f,sizeof mn);
pq.push({0,1});dist[1] = 0;
while(!pq.empty())
{
auto _ = pq.top();pq.pop();
int u = _.y,d = _.x;
if(st[u]) continue;
st[u] = 1;
for(int i = ver[u];~i;i = succ[i])
{
int v = edge[i],c1 = cost[i];
if(mn[v] > c1)
{
for(int j = ver[v];~j;j = succ[j])
{
int z = edge[j],c2 = cost[j];
if(dist[z] > d + (c1 + c2) * (c1 + c2))
{
dist[z] = d + (c1 + c2) * (c1 + c2);
pq.push({dist[z],z});
}
}
mn[v] = c1;
}
}
}
}
int main()
{
memset(ver,-1,sizeof ver);
int n,m;scanf("%d%d",&n,&m);
forn(i,1,m)
{
int u,v,w;scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
dijkstra();
forn(i,1,n) if(dist[i] == 0x3f3f3f3f) printf("-1 ");else printf("%d ",dist[i]);
return 0;
}