AtCoder Beginner Contest 368(ABC368)

rgw2010發表於2024-09-01

[ABC369C] Count Arithmetic Subarrays

題意:

判斷有多少個區間是等差數列(不能重排)。

\(1 \le n \times 10^5\)

思路:

賽時看錯題了,以為這個區間可以重排,卡了 8min,小丑了。

首先容易注意到,對於一個區間 \([l,r]\),若其是等差數列,則這個區間的子區間 \([l',r']\) 肯定也是子序列,造成的貢獻是 \(\frac{(r-l+1)(r-l+2)}{2}\)

那麼考慮求出所有極長等差區間,設 \(d_i = a_{i+1} - a_i\),若 \(i\) 能加入 \(i-1\) 的等差區間,當且僅當 \(d_i = d_{i-1}\);否則就要新開一個等差子區間。

注意最後答案要加 \(n-1\)

時間複雜度為 \(O(N)\)

完整程式碼:
#include<bits/stdc++.h>
#define Add(x,y) (x+y>=mod)?(x+y-mod):(x+y)
#define lowbit(x) x&(-x)
#define pi pair<ll,ll>
#define pii pair<ll,pair<ll,ll>>
#define iip pair<pair<ll,ll>,ll>
#define ppii pair<pair<ll,ll>,pair<ll,ll>>
#define fi first
#define se second
#define full(l,r,x) for(auto it=l;it!=r;it++) (*it)=x
#define Full(A) memset(A,0,sizeof(A))
#define open(s1,s2) freopen(s1,"r",stdin),freopen(s2,"w",stdout);
#define For(i,l,r) for(int i=l;i<=r;i++)
#define _For(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
typedef long double lb;
typedef double db;
typedef unsigned long long ull;
typedef long long ll;
bool Begin;
const int N=2e5+6;
inline ll read(){
    ll x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')
          f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline void write(ll x){
	if(x<0){
		putchar('-');
		x=-x;
	}
	if(x>9)
	  write(x/10);
	putchar(x%10+'0');
}
ll n,s,ans;
ll a[N],d[N];
int main(){
	n=read();
	For(i,1,n)
	  a[i]=read();
	For(i,1,n)
	  d[i]=a[i+1]-a[i];
	For(i,1,n){
		if(d[i]!=d[i-1]){
			ans+=s*(s+1)/2;
			s=1;
		}
		else
		  s++;		
	}
	ans+=s*(s+1)/2;
	write(ans+n-1);
	return 0;
}

[ABC369D] Bonus EXP

題意:

\(n\) 個物品依次從你身邊經過,第 \(i\) 個物品的貢獻為 \(a_i\)

若你選擇的第 \(j\) 個物品是 \(i\)

  • \(j\) 為偶數時:收益為 \(a_i \times 2\)

  • 否則收益為 \(a_i\)

問最大收益。

\(1 \le n \times 10^5\)

思路:

考慮動態規劃演算法。

注意到當前位置的取值只跟當前取的個數的奇偶性有關,則定義 \(dp_{i,0/1}\) 表示對於前 \(i\) 個物品取的物品個數對 \(2\) 取餘的結果為 \(0/1\) 的最大權值,則狀態轉移方程為:

\[dp_{i,0} = \max(dp_{i-1,1} + 2a_i,dp_{i-1,0}) \]

\[dp_{i,1} = \max(dp_{i-1,1},dp_{i-1,0} + a_i) \]

最後的答案是 \(\max(dp_{n,0},dp_{n,1})\),時間複雜度為 \(O(N)\)

完整程式碼:
#include<bits/stdc++.h>
#define Add(x,y) (x+y>=mod)?(x+y-mod):(x+y)
#define lowbit(x) x&(-x)
#define pi pair<ll,ll>
#define pii pair<ll,pair<ll,ll>>
#define iip pair<pair<ll,ll>,ll>
#define ppii pair<pair<ll,ll>,pair<ll,ll>>
#define fi first
#define se second
#define full(l,r,x) for(auto it=l;it!=r;it++) (*it)=x
#define Full(a) memset(a,0,sizeof(a))
#define open(s1,s2) freopen(s1,"r",stdin),freopen(s2,"w",stdout);
#define For(i,l,r) for(int i=l;i<=r;i++)
#define _For(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
typedef long double lb;
typedef double db;
typedef unsigned long long ull;
typedef long long ll;
bool Begin;
const ll N=2e5+10;
inline ll read(){
    ll x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')
          f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline void write(ll x){
	if(x<0){
		putchar('-');
		x=-x;
	}
	if(x>9)
	  write(x/10);
	putchar(x%10+'0');
}
ll n;
ll a[N],dp[N][2];
int main(){
	n=read();
	For(i,1,n)
	  a[i]=read();
	dp[1][1]=a[1],dp[1][0]=0;
	For(i,2,n){
		dp[i][0]=max(dp[i-1][1]+a[i]*2,dp[i-1][0]);
		dp[i][1]=max(dp[i-1][0]+a[i],dp[i-1][1]);
	}
	write(max(dp[n][0],dp[n][1]));
	return 0;
}

[ABC369E] Sightseeing Tour

題意:

給定一個 \(n\) 個節點的無向圖,共 \(q\) 次詢問,每次給定 \(k\) 個必須經過的邊,問從 \(1 \sim n\) 的最短路徑是多少。

\(1 \le n \le 500,1 \le q \le 3000,1 \le k \le 5\)

思路:

考慮先求出任意兩點的最短路徑 \(dis_{i,j}\),因為 \(n \le 500\),可以使用 Floyd 演算法 \(n^3\) 跑出。

然後對於每組詢問,必須要經過編號為 \(a_1,a_2,a_3,\cdots,a_k\) 的橋,定義編號為 \(i\) 的橋為 \((u_i,v_i,w_i)\)

同時注意到 \(k \le 5\) 非常小,考慮全排列出依次經過哪些橋,同時爆搜是先到達這個橋的左端還是右端即可。

時間複雜度為 \(O(N^3 + QK!2^K)\)

完整程式碼:
#include<bits/stdc++.h>
#define Add(x,y) (x+y>=mod)?(x+y-mod):(x+y)
#define lowbit(x) x&(-x)
#define pi pair<ll,ll>
#define pii pair<ll,pair<ll,ll>>
#define iip pair<pair<ll,ll>,ll>
#define ppii pair<pair<ll,ll>,pair<ll,ll>>
#define fi first
#define se second
#define full(l,r,x) for(auto it=l;it!=r;it++) (*it)=x
#define Full(a) memset(a,0,sizeof(a))
#define open(s1,s2) freopen(s1,"r",stdin),freopen(s2,"w",stdout);
#define For(i,l,r) for(int i=l;i<=r;i++)
#define _For(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
typedef long double lb;
typedef double db;
typedef unsigned long long ull;
typedef long long ll;
bool Begin;
const ll N=505,M=2e5+10;
inline ll read(){
    ll x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')
          f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline void write(ll x){
	if(x<0){
		putchar('-');
		x=-x;
	}
	if(x>9)
	  write(x/10);
	putchar(x%10+'0');
}
ll n,m,q,k,x,y,w,ans;
ll a[N],p[N],A[M],B[M],W[M];
ll dis[N][N];
void dfs(ll pos,ll pre,ll sum){
	if(sum>ans)
	  return ;
	if(pos==k+1){
		ans=min(ans,sum+dis[pre][n]);
		return ;
	}
	dfs(pos+1,A[a[p[pos]]],sum+dis[pre][B[a[p[pos]]]]+W[a[p[pos]]]);
	dfs(pos+1,B[a[p[pos]]],sum+dis[pre][A[a[p[pos]]]]+W[a[p[pos]]]);
}
int main(){
	n=read(),m=read();
	For(i,1,n){
		For(j,1,n){
			if(i==j)
			  continue;
			dis[i][j]=1e18;
		}
	}
	For(i,1,m){
		x=read(),y=read(),w=read();
		dis[x][y]=min(dis[x][y],w);
		dis[y][x]=min(dis[y][x],w);
		A[i]=x,B[i]=y,W[i]=w;
	}
	for(int k=1;k<=n;k++)
	  for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		  dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	q=read();
	while(q--){
		ans=1e18;
		k=read();
		For(i,1,k){
			a[i]=read();
			p[i]=i;
		}
		while(1){
			dfs(1,1,0);
			if(!next_permutation(p+1,p+k+1))
			  break;	
		}
		write(ans);
		putchar('\n');
	}
	return 0;
}

[ABC369F] Gather Coins

題意:

給定一個 \(h \times w\) 的棋牌,有 \(n\) 個位置上有一個棋子,其它位置沒有棋子,人每次可以往下或往左走,問從 \((1,1)\) 走到 \((n,n)\) 能收集到的最多的棋子數量。

\(1 \le h,w \le 2 \times 10^5,1 \le n \le \min(hw-2,2 \times 10^5)\)

思路:

考慮動態規劃,令 \(dp_i\) 表示到達第 \(i\) 個棋子最大權值,則狀態轉移方程為:

\[dp_i = \max [x_j \le x_i] [y_j \le y_i ] dp_j + 1 \]

這是二維偏序最佳化動態規劃問題,考慮先按 \(x\) 從小到大排序,若 \(x\) 相同則按 \(y\) 從小到大排序,那麼這樣對於 \(j < i\),自然都滿足 \(x_j \le x_i\),則狀態轉移方程降了一維:

\[dp_i = \max [y_j \le y_i] dp_j +1 \]

相當於詢問 \([1,y_i]\) 區間內的最大值,因為需要輸出路徑,所以我們還需要記錄一下最大值的位置,使用線段樹維護即可。

時間複雜度為 \(O(N \log N)\)

完整程式碼:
#include<bits/stdc++.h>
#define Add(x,y) (x+y>=mod)?(x+y-mod):(x+y)
#define lowbit(x) x&(-x)
#define pi pair<ll,ll>
#define pii pair<ll,pair<ll,ll>>
#define iip pair<pair<ll,ll>,ll>
#define ppii pair<pair<ll,ll>,pair<ll,ll>>
#define fi first
#define se second
#define full(l,r,x) for(auto it=l;it!=r;it++) (*it)=x
#define Full(a) memset(a,0,sizeof(a))
#define open(s1,s2) freopen(s1,"r",stdin),freopen(s2,"w",stdout);
#define For(i,l,r) for(int i=l;i<=r;i++)
#define _For(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
typedef long double lb;
typedef double db;
typedef unsigned long long ull;
typedef long long ll;
bool Begin;
const ll N=2e5+10;
inline ll read(){
    ll x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')
          f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline void write(ll x){
	if(x<0){
		putchar('-');
		x=-x;
	}
	if(x>9)
	  write(x/10);
	putchar(x%10+'0');
}
struct Node{
	ll x,y;
	bool operator<(const Node&rhs)const{
		if(x^rhs.x)
		  return x<rhs.x;
		return y<rhs.y;
	}
}a[N];
struct St{
	ll l,r;
	ll Max,id;
}X[N<<2];
ll h,w,n,id,cnt,ans;
ll dp[N],pre[N];
string S[N];
void pushup(ll k){
	X[k].Max=max(X[k<<1].Max,X[k<<1|1].Max);
	if(X[k].Max==X[k<<1].Max)
	  X[k].id=X[k<<1].id;
	else
	  X[k].id=X[k<<1|1].id;
}
void build(ll k,ll l,ll r){
	X[k].l=l,X[k].r=r;
	if(l==r)
	  return ;
	ll mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}
void add(ll k,ll i,ll v,ll id){
	if(X[k].l==i&&i==X[k].r){
		if(v>X[k].Max){
			X[k].Max=v;
			X[k].id=id;
		}
		return ;
	}
	ll mid=(X[k].l+X[k].r)>>1;
	if(i<=mid)
	  add(k<<1,i,v,id);
	else
	  add(k<<1|1,i,v,id);
	pushup(k);
}
pi query(ll k,ll l,ll r){
	if(X[k].l==l&&r==X[k].r)
	  return {X[k].Max,X[k].id};
	ll mid=(X[k].l+X[k].r)>>1;
	if(r<=mid)
	  return query(k<<1,l,r);
	else if(l>mid)
	  return query(k<<1|1,l,r);
	else{
		auto x=query(k<<1,l,mid),y=query(k<<1|1,mid+1,r);
		pi ans;
		ans.fi=max(x.fi,y.fi);
		if(ans.fi==x.fi)
		  ans.se=x.se;
		else
		  ans.se=y.se;
		return ans;
	}
}
int main(){
	h=read(),w=read(),n=read();
	a[0]={1,1};
	a[n+1]={h,w};
	For(i,1,n)
	  a[i]={read(),read()};
	build(1,1,w);
	sort(a+1,a+n+1);
	dp[1]=ans=id=1;
	add(1,a[1].y,1,1);
	For(i,2,n){
		auto t=query(1,1,a[i].y);
		dp[i]=t.fi+1;
		pre[i]=t.se;
		add(1,a[i].y,dp[i],i);
		if(dp[i]>ans){
			ans=dp[i];
			id=i;
		}
	}
	pre[n+1]=id;
	id=n+1;
	while(1){
		ll x=pre[id];
		++cnt;
		ll sx=a[x].x,sy=a[x].y,tx=a[id].x,ty=a[id].y;
		For(i,1,tx-sx)
		  S[cnt]+='D';
		For(i,1,ty-sy)
		  S[cnt]+='R';
		id=x;
		if(!x)
		  break;
	}
	write(ans);
	putchar('\n');
	_For(i,1,cnt)
	  for(auto c:S[i])
		putchar(c);
	return 0;
}

[ABC369G] As far as possible

題意:

給定一個 \(n\) 個節點且有邊權的樹,對於每個 \(k \in [1,n]\) 求:

  • 選取 \(k\) 條從根結點出發到葉子結點的簡單路徑,求這些路徑的並集上所有結點的點權之和的最大值。

\(1 \le n \le 2 \times 10^5\)

思路:

雙倍經驗。

明顯有一個貪心的思路:

  • 每次取葉子到根的路徑權值和最大的。

  • 清空該鏈上的點的點權。

模擬 \(n\) 次即可,但是複雜度不可取,考慮資料結構最佳化。

將葉子節點按 dfn 序從小到大編號,令 \(l_u,r_u\) 表示 \(u\) 子樹內葉子節點的編號在 \([l_u,r_u]\) 間。

每次找到路徑權值和最大的那個葉子節點,可以直接暴力跳父親清空路徑上的點,每次清空就是將區間 \([l_u,r_u]\) 內的葉子結點到根的權值和減去 \(w_u\)

若遇到一個點已經被清空了,故該點到根節點路徑上的點肯定也被清空了,就可以直接退出了;這樣每個點做多隻會清空一次。

考慮使用線段樹維護上述操作,時間複雜度均攤為 \(O(N \log N)\)

完整程式碼:
#include<bits/stdc++.h>
#define Add(x,y) (x+y>=mod)?(x+y-mod):(x+y)
#define lowbit(x) x&(-x)
#define pi pair<ll,ll>
#define pii pair<ll,pair<ll,ll>>
#define iip pair<pair<ll,ll>,ll>
#define ppii pair<pair<ll,ll>,pair<ll,ll>>
#define fi first
#define se second
#define full(l,r,x) for(auto it=l;it!=r;it++) (*it)=x
#define Full(a) memset(a,0,sizeof(a))
#define open(s1,s2) freopen(s1,"r",stdin),freopen(s2,"w",stdout);
#define For(i,l,r) for(int i=l;i<=r;i++)
#define _For(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
typedef long double lb;
typedef double db;
typedef unsigned long long ull;
typedef long long ll;
bool Begin;
const ll N=2e5+10;
inline ll read(){
    ll x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')
          f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return x*f;
}
inline void write(ll x){
	if(x<0){
		putchar('-');
		x=-x;
	}
	if(x>9)
	  write(x/10);
	putchar(x%10+'0');
}
struct Node{
	ll l,r;
	ll tag;
	ll Max;
	ll id;
}X[N<<2];
ll n,k,cnt,ans;
ll a[N],Ans[N],l[N],r[N],d[N],id[N],fa[N];
vector<pi> E[N];
map<pi,ll> F;
bool f[N];
void add(ll u,ll v,ll w){
	E[u].push_back({v,w});
	E[v].push_back({u,w});
}
void dfs(ll u,ll f){
	l[u]=n,r[u]=1;
	bool F=1;
	for(auto t:E[u]){
		ll v=t.fi,w=t.se;
		if(v==f)
		  continue;
		fa[v]=u;
		d[v]=d[u]+w;
		dfs(v,u);
		l[u]=min(l[u],l[v]);
		r[u]=max(r[u],r[v]);
		F=0;
	}
	if(F){
		id[++cnt]=u;
		l[u]=r[u]=cnt;
		a[cnt]=d[u];
	}
}
void pushup(ll k){
	X[k].Max=max(X[k<<1].Max,X[k<<1|1].Max);
	if(X[k].Max==X[k<<1].Max)
	  X[k].id=X[k<<1].id;
	else
	  X[k].id=X[k<<1|1].id;
}
void update(ll k,ll v){
	X[k].Max+=v;
	X[k].tag+=v;
}
void push_down(ll k){
	if(X[k].tag){
		update(k<<1,X[k].tag);
		update(k<<1|1,X[k].tag);
		X[k].tag=0;
	}
}
void build(ll k,ll l,ll r){
	X[k].l=l,X[k].r=r;
	if(l==r){
		X[k].Max=a[l];
		X[k].id=l;
		return ;
	}
	ll mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	pushup(k);
}
void update(ll k,ll l,ll r,ll v){
	if(X[k].l==l&&r==X[k].r){
		update(k,v);
		return ;
	}
	push_down(k);
	ll mid=(X[k].l+X[k].r)>>1;
	if(r<=mid)
	  update(k<<1,l,r,v);
	else if(l>mid)
	  update(k<<1|1,l,r,v);
	else{
		update(k<<1,l,mid,v);
		update(k<<1|1,mid+1,r,v);
	}
	pushup(k);
}
void solve(ll u){
	while(!f[u]&&fa[u]){
		f[u]=1;
		update(1,l[u],r[u],-F[{u,fa[u]}]);
		u=fa[u];
	}
}
int main(){
	n=read();
	for(int u,v,w,i=1;i<n;i++){
		u=read(),v=read(),w=read();
		F[{u,v}]=F[{v,u}]=w;
		add(u,v,w);
	}
	dfs(1,0);
	build(1,1,cnt);
	For(i,1,n){
		if(X[1].Max>0)
		  ans+=X[1].Max;
		if(X[1].Max>0)
		  solve(id[X[1].id]);
		write(ans*2);
		putchar('\n');
	}
	return 0;
}

相關文章