1. 程式人生 > 其它 >義烏集訓7.10 contest 3題解

義烏集訓7.10 contest 3題解

2021.7.10 Contest 題解

T1:

Description:

​ Alice 和 Bob 在玩石頭剪刀布,他們每個人寫出一個序列。 Alice 寫出了 \(n\) 個數, Bob 寫出了 \(n\) 個數。 其中 \(0\) 代表石頭,\(1\) 代表剪刀,\(2\) 代表布,\(0\)\(1\)\(1\)\(2\)\(2\)\(0\)

​ 他們總共進行 \(k\) 輪遊戲,第一輪選擇第一個數字,後面每一輪兩個人都選擇序列的下一個數進行比賽(序列結尾的下 一個位置在序列開頭)。 一個人的積分為其贏的次數加上額外積分。

​ 額外積分:對於每一個 \([1,k-x+1]\)

內的正整數 ,滿足某人從第 \(i\) 輪到第 \(i+x-1\) 輪都贏,都會讓這個人獲得 \(1\) 的額外積分。

​ 問 Alice 和 Bob 每人積分是多少。

Input:

​ 第一行三個數 \(n,k,x\)

​ 第二行 \(n\) 個不大於 \(2\) 的非負整數。

​ 第三行 \(n\) 個不大於 \(2\) 的非負整數。

Output:

​ 一行兩個整數表示 Alice 和 Bob 每人積分。

Sample1 Input:

5 10 2
1 1 0 2 0
1 0 2 2 0

Sample1 Output:

0 6

Hint:

對於 \(20\%\) 的資料,\(n,k \leq 1000,x=n+1\)

對於另外 \(20\%\) 的資料,\(x=n+1\)

對於另外 \(20\%\) 的資料,\(k \leq 500000\)

對於 \(100\%\) 的資料,\(1 \leq n \leq 500000,1 \leq k,x \leq 10^{18}\)

題目分析:

​ 小清新模擬題,先 \(O(n)\) 預處理出一個迴圈之後的雙方得分,然後隨便亂搞即可。

程式碼如下(馬蜂很醜,不喜勿噴)——

#include<bits/stdc++.h>
#define N 500005
#define LL long long
using namespace std;
LL n,ans1,ans2,m,x,a[N],s1[N],s2[N],S1[N],S2[N];
inline int read(){
	int ret=0,f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();return ret*f;
}
int main(){
	freopen("a.in","r",stdin);freopen("a.out","w",stdout);
	cin>>n>>m>>x;for(register int i=1;i<=n;i++) a[i]=read();for(register int i=1,xx;i<=n;i++){
		xx=read();if(a[i]==0){if(xx==1) a[i]=1;else if(xx==2) a[i]=-1;else a[i]=0;continue;}
		if(a[i]==1){if(xx==2) a[i]=1;else if(xx==0) a[i]=-1;else a[i]=0;continue;}if(xx==0) a[i]=1;else if(xx==1) a[i]=-1;else a[i]=0;
	}
	for(register int i=1;i<=n;i++){s1[i]=s1[i-1],s2[i]=s2[i-1];if(a[i]==1) s1[i]++;else if(a[i]==-1) s2[i]++;}
	LL X=m/n,Y=m%n;ans1=X*s1[n]+s1[Y],ans2=X*s2[n]+s2[Y];if(x>m){cout<<ans1<<' '<<ans2<<'\n';return 0;}
	if(x>=n){if(s1[n]==n) ans1+=m-x+1;else if(s2[n]==n) ans2+=m-x+1;cout<<ans1<<' '<<ans2<<'\n';return 0;}
	for(register int i=1;i<=n-x+1;i++){S1[i]=S1[i-1],S2[i]=S2[i-1];if(s1[i+x-1]-s1[i-1]==x) S1[i]++;else if(s2[i+x-1]-s2[i-1]==x) S2[i]++;}
	for(register int i=n-x+2;i<=n;i++){S1[i]=S1[i-1],S2[i]=S2[i-1];if(s1[n]-s1[i-1]+s1[i+x-n-1]==x) S1[i]++;else if(s2[n]-s2[i-1]+s2[i+x-n-1]==x) S2[i]++;}
	X=(m-x+1)/n,Y=(m-x+1)%n;ans1+=X*S1[n]+S1[Y],ans2+=X*S2[n]+S2[Y];cout<<ans1<<' '<<ans2<<'\n';return 0;
}

T2:

Description:

​ 有 \(t\) 次詢問,每次給你一個數 \(n\) ,求在 \([1,n]\) 內約數個數最多的數的約數個數。

Input:

​ 第一行一個正整數 \(t\)

​ 之後 \(t\) 行,每行一個正整數 \(n\)

Output:

​ 輸出 \(t\) 行,每行一個整數,表示答案。

Sample1 Input:

5
13
9
1
13
16

Sample1 Output:

6
4
1
6
6

Hint:

對於 \(100\%\) 的資料,\(t\leq 500,1\leq n \leq 10^{18}\)

題目分析:

​ 注意到要使約數個數儘可能的多,其可用的質因數小於等於41(其實37就夠了),跑個爆搜就能過了。

​ 但是作為追求更優解的人,我們肯定不滿足於此。我們考慮揹包。

​ 設 \(f_{i,j}\) 表示用到了第 \(i\) 個質數,當前約數個數為 \(j\) 所對應的最小的 \(n\),那麼對於每一個 \(j\) 只需判斷 \(f_{12,j}\) 是否小於 \(n\) 即可。

程式碼如下(馬蜂很醜,不喜勿噴)——

#include<bits/stdc++.h>
#define N 200005
#define LL long long
using namespace std;
LL n[N],f[20][N],pri[20]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59};int T,tot,ans;LL Max;
inline int read(){
	int ret=0,f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();return ret*f;
}
int main(){
//	freopen("b.in","r",stdin);freopen("b.out","w",stdout);
	T=read();for(register int i=1;i<=T;i++) cin>>n[i],Max=max(Max,n[i]);int up=110000;if(Max<=1e9) up=10000;
	f[0][1]=1;for(register int i=1;i<=12;i++) for(register int j=1;j<=up;j++) if(f[i-1][j]){
		LL x=f[i-1][j];if(!f[i][j]) f[i][j]=x;else f[i][j]=min(f[i][j],x);
		int X=0;while(x<=Max/pri[i]){x*=pri[i],X++;if(!f[i][j*(X+1)]) f[i][j*(X+1)]=x;else f[i][j*(X+1)]=min(f[i][j*(X+1)],x);}
	}
	for(register int i=1;i<=T;i++){int ans=1;for(register int j=up;j;j--) if(f[12][j]&&f[12][j]<=n[i]){ans=j;break;}cout<<ans<<'\n';}return 0;
}

T3:

Description:

​ 給你一個長為 \(n\) 的序列 \(a\) 和一個常數 \(k\)

​ 有 \(m\) 次詢問,每次查詢一個區間 \([l,r]\) 內所有數最少分成多少個連續段,使得每段的和都 \(\leq k\)

​ 如果這一次查詢無解,輸出 "Chtholly",輸出的字串不包含引號。

Input:

​ 第一行三個數 \(n,m,k\)

​ 第二行 \(n\) 個數表示這個序列 \(a_i\)

​ 之後 \(m\) 行,每行給出兩個數 \(l,r\) 表示一次詢問。

Output:

​ 輸出 \(m\) 行,每行一個整數,表示答案。

Sample1 Input:

5 5 7
2 3 2 3 4
3 3
4 4
5 5
1 5
2 4

Sample1 Output:

1
1
1
2
2

Hint:

對於 \(100\%\) 的資料,滿足 \(1\le n,m\le10^6,1\le a_i,x\le10^9\)

題目分析:

​ 倍增套路題。對於每個 \(i\) 我們可以二分或者 two-pointers 預處理出最大的 \(j\) ,滿足 \(\sum_{k=i}^{j}{a_k} \leq x\)

​ 然後對於每一組詢問,從 \(l\) 開始倍增,直到剛好大於 \(r\) ,如果沒法大於 \(r\),則無解。

程式碼如下(馬蜂很醜,不喜勿噴)——

#include<bits/stdc++.h>
#define N 1000005
#define LL long long
using namespace std;
int n,m,tot,f[N][24];LL S[N],K;
inline int read(){int ret=0,f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}while(isdigit(ch)) ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();return ret*f;}
int main(){
	freopen("c.in","r",stdin);freopen("c.out","w",stdout);
	n=read(),m=read(),K=read();for(register int i=1;i<=n;i++) S[i]=S[i-1]+(LL)read();
	for(register int i=1;i<=n;i++){if(S[i]-S[i-1]>K){f[i][0]=i;continue;}int l=i,r=n,x=i;while(l<=r){int mid=l+r>>1;if(S[mid]-S[i-1]<=K) x=mid,l=mid+1;else r=mid-1;}f[i][0]=x+1;}
	for(register int x=n;x;x--) for(register int i=1;i<=19;i++) f[x][i]=f[f[x][i-1]][i-1];while(m--)
	{int l=read(),r=read(),res=0;for(register int i=19;~i;i--) if(f[l][i]&&f[l][i]<=r) l=f[l][i],res+=(1<<i);if(f[l][0]<=r) puts("Chtholly");else cout<<res+1<<'\n'; }return 0;
}

T4:

Description:

​ 給定一棵 \(n\) 個節點的樹,第 \(i\) 個點的編號為 \(i\) ,第 \(j\) 條邊的編號為 \(j\)
​ 有 \(m\) 次查詢,每次給出 \(l,r\) ,查詢如果只保留樹上點編號在 \([l,r]\) 內的點,邊編號在 \([l,r]\) 內的邊,有多少點連通塊。
​ 此時點 \(a\)\(b\) 連通等價於 \(l \leq a,b \leq r\)\(a,b\) 在樹上的簡單路徑中所有點與邊編號都在 \([l,r]\) 之間。

Input:

​ 第一行兩個數 \(n,m\)

​ 之後 \(n-1\) 行,編號從 \(1\) 開始,第 \(i\) 行三個數 \(x,y\) 表示編號為 \(i\) 的邊連線著點 \(x,y\)

​ 之後 \(m\) 行,每行兩個數 \(l,r\) 表示詢問區間 \([l,r]\)

Output:

​ 對每次詢問輸出一行一個數表示答案。

Sample1 Input:

10 10
1 2
2 3
1 4
1 5
6 4
7 2
8 3
1 9
3 10
1 6
6 7
1 8
3 3
7 10
4 10
8 9
2 3
5 8
5 9

Sample1 Output:

1
2
1
1
4
6
2
1
4
5

Hint:

​ 對於其中 \(30\%\) 的資料,\(n,m \leq 10^3\).

​ 對於其中 \(50\%\) 的資料,\(n\leq 10^3\).

​ 對於另外 \(20\%\) 的資料,\(n,m\leq 10^5\).

​ 對於全部資料,\(1 \leq n,m \leq 10^6\).

題目分析:

​ 容易想到,對於一次詢問,答案=點數-邊數=\((r-l+1)-有用的邊數\).

​ 何為有用的邊?即對於邊 \(i\) ,滿足 \(l\le i \le r\) ,並且 \(i\) 連線的兩個點 \(x,y\)\(l \leq x,y \leq r\)

​ 也就是說對於邊 \(i\) 必須滿足 $l\le min{(i,x,y) \le max{(i,x,y)} \le r} $。

​ 於是這道題就變成了二維數點問題,離線之後用樹狀陣列維護即可。

程式碼如下(馬蜂很醜,不喜勿噴)——

#include<bits/stdc++.h>
#define N 1000005
using namespace std;
int n,m,S[N],ans[N];struct node{int l,r;}p[N];struct query{int id,l,r;}q[N];
inline bool cmp(const node x,const node y){return x.l>y.l;} inline bool cmp2(const query x,const query y){return x.l>y.l;}
inline void U(int x){for(register int i=x;i<=n;i+=i&-i) S[i]++;}inline int Q(int x){int y=0;for(register int i=x;i;i-=i&-i) y+=S[i];return y;}
inline int read(){int ret=0,f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}while(isdigit(ch)) ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();return ret*f;}
int main(){
	freopen("d.in","r",stdin);freopen("d.out","w",stdout);
	n=read(),m=read();for(register int i=1,x,y;i<n;i++) x=read(),y=read(),(x>y)&&(swap(x,y),0),p[i].l=min(i,x),p[i].r=max(i,y);int now=1;
	sort(p+1,p+n,cmp);for(register int i=1;i<=m;i++) q[i].id=i,q[i].l=read(),q[i].r=read();sort(q+1,q+m+1,cmp2);
	for(register int i=1;i<=m;i++){int id=q[i].id,L=q[i].l,R=q[i].r;while(now<n&&L<=p[now].l) U(p[now].r),now++;ans[id]=(R-L+1)-Q(R);}
	for(register int i=1;i<=m;i++) cout<<ans[i]<<'\n';return 0;
}