[題解]牛客小白月賽6
題目比較基礎,但涵蓋的知識點很多,最後兩道題是賽後寫的。
完整程式碼請搜尋LittleFall的提交記錄
A-鯤
兩人在周長l的圓環上比賽游泳一週,A的速度始終為a,B的速度為b,兩人同時從起點同向出發,當兩人圓周距離大於k時,B可以選擇反向遊,先回到起點的人勝。給出l,k,a,b,問兩者回到起點的時間差。
A的到達時間恆定為l/a,B只有兩種策略:1.一直向前,2.向前一段再向後。
若則B只能選擇策略1,到達時間為。
當時,兩者相距為的時間為,策略2總花費時間為。
選擇策略2有一個條件,即B到達終點時A距離終點至少還有
複雜度O(1)
ll l = read(),k=read(),a=read(),b=read();
double tb = (double)l/b;
if( 2*k<l && b<a && 2*k*a<=(l-k)*(a-b) )
tb = min(tb, 2.0*k/(a-b));
printf("%.2f\n", tb - (double)l/a );
B-鵬
n個整數的序列,求先上升後下降的段出現了幾次。
判斷上升,再判斷下降,計數。
O(n)
int n =read(),ans = 0;
int now = read(),up=0;
for(int i =1;i<n;i++)
{
int t=read();
if(t>now)
up=1;
else if(t<now)
{
if(up==1)
{
up=0;
ans++;
}
}
now =t;
}
printf ("%d\n",ans );
C-桃花
給出一棵樹,求樹的直徑(最長的鏈)長度。
從根節點dfs一次,找到最遠的點x。再從x開始dfs一次,找到最遠的點y,則x到y是樹的一條直徑。
證明可以百度,大意是反證距離任意點最遠的點都是直徑的端點。
O(n)
vector<int> save[M];
int dis[M];
int ans,xp = -1;
void find(int p)
{
if(dis[p]>xp)
{
xp = dis[p];
ans = p;
}
for(auto x:save[p])
{
if(dis[x]==-1)
{
dis[x] = dis[p] + 1;
find(x);
}
}
}
int main(void)
{
int n = read();
for(int i=1;i<n;i++)
{
int a = read(),b=read();
save[a].push_back(b);
save[b].push_back(a);
}
memset(dis,-1,sizeof(dis));
dis[1] = 0;
find(1);
memset(dis,-1,sizeof(dis));
xp = -1;
dis[ans] = 0;
find(ans);
printf("%d\n",xp+1 );
return 0;
}
D-字串絲帶
開一個數組表示遍歷到當前位置時出現過的各個字母數量,再開一個數組記錄每個位置的答案。
O(n+m)
char save[M];
int record[256];
int ans[M];
int main(void)
{
int n = read(), m=read();
scanf("%s",save+1);
for(int i=1;save[i];i++)
{
record[save[i]]++;
ans[i] = record[save[i]];
}
for(int i=0;i<m;i++)
{
printf("%d\n",ans[read()] );
}
return 0;
}
E-對弈
五子棋,給出下棋順序,判斷誰先贏。
每走一步需要判斷輸贏,我的方法是以當前走的棋子為中心判斷左右9格,上下9格,左上右下9格,左下右上9格是否有連續的5個相同顏色棋子。將陣列整體向右下平移即可不考慮邊界。
const int go[4][2] = {{1,1},{0,1},{1,0},{-1,1}};
//1,1 - n,n對應 5,5 - n+4,n+4, 所有棋子橫縱座標+4
int save[M][M];
int main(void)
{
int n=read(),m=read();
int ans = 0, ansstep = m, player = -1; //1表示htbest,-1表示whz
for(int step = 1;step<=m; step++)
{
player *= -1;
int x=read()+4,y=read()+4;
save[x][y] = player;
for(int k = 0;k<4;k++)
{
int px = x - 4*go[k][0], py = y-4*go[k][1];
int lx = 0,alx = 0;
for(int cnt = 0;cnt<9;cnt++)
{
if(save[px][py]==player)
{
lx++;
alx = max(alx,lx);
}
else
{
lx = 0;
}
px +=go[k][0],py+=go[k][1];
}
if(alx>=5)
{
ans = player;
ansstep = step;
goto end;
}
}
}
end:
if(ans==0)
printf("UNK %d\n",m);
else if(ans==1)
printf("HtBest %d\n",ansstep );
else
printf("WHZ %d\n",ansstep );
return 0;
}
F-發電
1e6個元素,1e6個操作,三種:1.單點乘,2.單點除,3.區間求積,模1e9+7輸出
樹狀陣列改造一下,將加換成乘,0換成1,加上取模,求區間積時用之後的除以之前的。
除以一個數就是乘以它的逆元,用費馬小定理。
複雜度
class BinTree: vector<ll>
{
public:
explicit BinTree(int k = 0) //預設初始化一個能儲存k個元素的空樹狀陣列
{
assign(k + 1, 1); //有效下標從1開始,0僅作邏輯用處
}
int lowbit(int k){return k & -k;}
ll sum(int k)//求第1個元素到第n個元素的和
{
return k > 0 ? (sum(k - lowbit(k)) * at(k))%MOD : 1;
}
int last()//返回最後一個元素下標
{
return size() - 1;
}
void add(int k, ll w) //為節點k加上w
{
if(k > last())return;
at(k) *= w;
at(k) %= MOD;
add(k + lowbit(k), w);
}
};
ll power(ll a, ll b)
{
ll ans = 1;
while(b)
{
if(b & 1) ans = ans * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return ans;
}
ll inv(ll a){
return power(a, MOD - 2);
}
int main(void)
{
int n=read(),m=read();
BinTree bt(n);
while(m--)
{
int op=read(),x=read(),y=read();
if(op==1)
bt.add(x,y);
else if(op==2)
bt.add(x,inv(y));
else
{
ll ans = bt.sum(y) * inv(bt.sum(x-1)) % MOD;
cout<<ans<<endl;
}
}
return 0;
}
G-指紋鎖
維護一個儲存著值在1e7以內的集合,1e6個操作,包括三種:
1.新增x,若集合內有值與x相差k以內,忽略本次操作;
2.刪除x,刪除集合內所有與x相差k以內的數;
3.查詢x,如果集合內有與x相差k以內的數則輸出Yes,否則輸出No.
用set,預先新增一個特別大的數如1e9+7,如果lower_bound(x-k)<=x+k,說明集合內有值與x相差k以內。
複雜度
set<int> st;
int select(int val)
{
auto it = st.lower_bound(val);
if(it==st.end()) return MOD;
return *it;
}
int main(void)
{
int m=read(),k=read();
char op[10];
int val;
for(int i = 0 ;i<m;i++)
{
scanf("%s%d",op,&val);
if(op[0]=='a')
{
if(select(val-k) > val+k)
st.insert(val);
}
else if(op[0]=='d')
{
int pval = select(val-k);
while(pval<=val+k)
{
st.erase(pval);
pval = select(val-k);
}
}
else
{
printf("%s\n",select(val-k)<=val+k?"Yes":"No" );
}
}
return 0;
}
H-挖溝
最小生成樹。
按最小生成樹做AC了,但是題目中說要求∑d[i][j]最小,我無法證明這個問題可以轉換成最生成樹問題。
Kruskal,O(ElogE)
struct Edge
{
int x,y,p;
}save[M];
int fa[M];
int getfa(int p)
{
return p==fa[p]?p:fa[p]=getfa(fa[p]);
}
int main(void)
{
int n=read(),m=read();
for(int i=0;i<m;i++)
{
save[i].x=read();
save[i].y=read();
save[i].p=read();
}
sort(save,save+m,[](Edge &a,Edge &b){return a.p<b.p;});
for(int i =1;i<=n;i++)
fa[i]=i;
int ans = 0;
for(int i=0;i<m;i++)
{
int x= getfa(save[i].x),y=getfa(save[i].y);
if(x!=y)
{
ans+=save[i].p;
fa[y]=x;
}
}
cout <<ans<<endl;
return 0;
}
I-公交線路
給出一張圖,求s點到t點的最短距離
dijkstra
複雜度O(N^2)
//鏈式前向星
int tot = 0;
int fst[N];
int pnt[M],nxt[M],pri[M];
void add(int x, int y, int prior)
{
pnt[++tot] = y;
pri[tot] = prior;
nxt[tot] = fst[x];
fst[x] = tot;
}
//dijkstra
int dis[N],vis[N];
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int n=read(),m=read(),s=read(),t=read();
for(int i =0;i<m;i++)
{
int x = read(),y=read(),p=read();
add(x,y,p);
add(y,x,p);
}
//dijkstra
memset(dis,0x3f,sizeof(dis));
dis[s] = 0, vis[s]=1;
int now = s;
while(now!=t)
{
for(int i = fst[now];i;i=nxt[i])
{
int y = pnt[i];
dis[y] = min(dis[y],dis[now]+pri[i]);
}
int xm = INT_MAX, tmp = 0;
for(int i =1;i<=n;i++)
if(vis[i]==0&&dis[i]<xm)
tmp = i, xm = dis[i];
now = tmp;
vis[now] = 1;
}
printf("%d\n",vis[t]?dis[t]:-1 );
return 0;
}
J-洋灰三角
給定n,k,p,數列,求的前n項和
(感謝我的大佬隊友們幫我賽後想清了公式)
如果,那麼,等差數列,
否則,,等比數列,最後算得
同樣需要快速冪和逆元,複雜度O(1)
ll power(ll a, ll b)
{
ll ans = 1;
while(b)
{
if(b & 1) ans = ans * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return ans;
}
ll inv(ll a){
return power(a, MOD - 2);
}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
ll n = rea