义乌集训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;
}
posted @ 2021-07-22 14:12  OdtreePrince  阅读(259)  评论(1编辑  收藏  举报