牛客挑战赛83

牛客练习赛83

A追求女神

注意到小L可以准时到达当且仅当,时间与两个位置的莫比雪夫距离之差是2的倍数。
然后直接判断即可。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int read(){
	int sum=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		sum=sum*10+ch-'0';
		ch=getchar();
	}
	return sum*f;
}
int main(){
	int T=read();
	while(T--){
		int n=read();
		int ans=1;
		int t=0,x=0,y=0;
		int x0,y0,t0;
		for(int i=1;i<=n;i++){
			x0=x,y0=y,t0=t;
			t=read(),x=read(),y=read();
			if(abs(x-x0)+abs(y-y0)>abs(t-t0)){
				ans=0;
                continue;
			}
			else if((abs(t-t0)-abs(x-x0)-abs(y-y0))%2!=0){
				ans=0; 
				continue;
			}
		}
		if(ans==1)printf("Yes\n");
		else printf("No\n");
	}
	return 0;
} 

B 计算几何

\(T<=10 \ \ l,r<=10^{18}\)

数位DP可以解决。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
using namespace std;
#define int long long
int read(){
	int sum=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
	return sum*f;
}
int m[100];
void init(){
	m[0]=1;
	for(int i=1;i<=61;i++)m[i]=m[i-1]*2;
}
int get(int x){
	int ans=0,cnt=0;
	for(int i=61;i>=1;i--){
		if(x>=m[i])ans+=m[i-1],x-=m[i],cnt++;
	}
	if(cnt%2==1||x==1)ans++;
	return ans;
}
signed main(){
	init();
	int T=read();
	while(T--){
		int l=read(),r=read();
		printf("%lld\n",get(r)-get(l-1));
	}
	return 0;
}

但是通过求ans的过程发现其实整个过程可以化简

C 集合操作


显然可以把集合中的数用\(a+b*p \ (0<=a<p)\)表示。
这样表示有两个好处:
1、\(b\)大的数比\(b\)小的数要大。
2、每一次操作就是使集合中的数的\(b\)减一。

可以将题目简化成割韭菜。
二分求出最多割韭菜的高度(粗割韭菜)。
如果这个时候\(k\)还有剩余就在最高的哪些韭菜中找\(a\)最大的割。
复杂度\(O(log(long long)*n)\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define N 1010000
#define int long long
int c[N];
int read(){
	int sum=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
	return sum*f;
}
struct Num{
	int a;
	int b;
}num[N];
bool operator <(Num x,Num y){
	return x.a>y.a;
}
signed main(){
	int n=read(),k=read(),p=read();
	if(p==0||k==0){
		for(int i=1;i<=n;i++)c[i]=read();
		sort(c+1,c+1+n);
		for(int i=1;i<=n;i++)printf("%lld ",c[i]);
		return 0;
	}
	for(int i=1;i<=n;i++){
		int x=read();
		num[i].a=x%p;
		num[i].b=x/p; 
	} 
	int ans=0;
	int L=-(1e18),R=(1e18);
	while(L<=R){
		int mid=(L+R)/2;
		int cnt=0; 
		for(int i=1;i<=n;i++){
			cnt+=((num[i].b-mid)>0?(num[i].b-mid):0);
			if(cnt>k)break;
		}
		if(cnt<=k)R=mid-1;
		else {
			ans=mid;
			L=mid+1;
		}
	}
	ans++;
	int cnt=0;
	for(int i=1;i<=n;i++){
		cnt+=max(num[i].b-ans,(int)0);
		num[i].b=min(num[i].b,ans);
	}
	k-=cnt;
	sort(num+1,num+1+n);
	for(int i=1;i<=n;i++){
		if(k==0)break;
		if(num[i].b==ans)num[i].b-=1,k--;
	}
	for(int i=1;i<=n;i++)c[i]=num[i].b*p+num[i].a; 
	sort(c+1,c+1+n);
	for(int i=1;i<=n;i++)printf("%lld ",c[i]);
	return 0;
} 

D 数列递推


考虑\(i\ mod\ j\)实际上等价于\(i-j*\lfloor\frac{i}{j}\rfloor\)
\(\lfloor\frac{i}{j}\rfloor\)可以联想到整除分块。
可以记录一个前缀和数组\(sum[i][j]\)代表以第\(i\)个数为结尾两个数之间间隔\(j\)\(f\)的前缀和。
只记录\(\lfloor\frac{i}{j}\rfloor<=sqrt(n)\)时的前缀和。
对于每一个\(i\)
对于\(\lfloor\frac{i}{j}\rfloor<=sqrt(i)\)的部分用前缀和求解。
对于\(\lfloor\frac{i}{j}\rfloor>sqrt(n)\)的部分可以证明满足这种条件的j不超过sqrt(i)个,可直接累加。
复杂度\(O(n\sqrt[2]{n})\)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define int long long
#define p 998244353
#define N 101000
int sum[N][333];
int f[N];
int read(){
	int sum=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
	return sum*f;
} 
signed main(){
	int n=read();
	f[0]=read();
	for(int i=1;i<=n;i++)sum[0][i]=f[0];
	for(int i=1;i<=n;i++){
		f[i]+=f[0];
		for(int l=2,r;l<=i;l=r+1){
			r=i/(i/l);
			if(i/l>sqrt(n)){
				for(int j=l;j<=r;j++)
					f[i]=(f[i]+f[i%j])%p;
				continue;
			}
			f[i]=(f[i]+sum[i-l*(i/l)][i/l]-sum[i-r*(i/l)][i/l]+f[i-i/l*r]+p)%p;
		}
		for(int j=1;j<=sqrt(n);j++)
			if(i>=j)sum[i][j]=(sum[i-j][j]+f[i])%p;
			else sum[i][j]=f[i];
	}
	for(int i=1;i<=n;i++)printf("%lld ",f[i]);
	return 0;
}

E 小L的疑惑



当年NOIP的题,现在居然不会了。
不能表示的第一大是\(a*b-a-b\)
可以用同余类得出。设同余类的大小为\(b-1\)\(a\)\(b\)互质,每次会填上一个同余类的空,不算\(0\),填满要\(b-1\)次。
对于每个空(即\(mod\ b\)结果相同的一组数)单独考虑,第一次填上表示的数显然是这组数中可以用\(a\)\(b\)表示的最小的。然后再减一次模数\(b\)就是最大不能表示的数。
然后找到每组数中最大的。
显然就是同余类最后被填上的空所代表的的那组数即\((b-1)*a-b\)

还有一个比较好的数学解法
小凯的疑惑数学解法

NOIP这题解法很多,但是大多都看不懂

然后考虑怎么求第k大。

同样考虑同余类。同样考虑每一个空分开。
只需要找到每一组最大的那一个不能表示的数\(x_i\)\(x_i-b\)同样不能表示,这样就能找到全部的不能表示的数。
然后想怎么找到每一组最大的那一个不能表示的数。
根据求第一大时的结论

对于每个空(即\(mod\ b\)结果相同的一组数)单独考虑,第一次填上表示的数显然是这组数中可以用\(a\)\(b\)表示的最小的。然后再减一次模数\(b\)就是最大不能表示的数。

只需找到第一次填上的数也就是\((b-x)*a\)
综上可以发现,一个数不能表示当且仅当这个数可以表示为\(ab-a-b-x_1a-x_2b(x_1,x_2 \in\mathbb{N})\),也就是最大不能表示的数减去若干个\(a\)\(b\)

具体的话,

假设 a<b 由于答案不能算重则当减去\(b\)时就不能在减去\(a\)

维护两个队列,一个队列表示当前能减去\(a\),另外仅能减去\(b\),那么每次在两个队头取最大的比较即可。

正确性类比\(NOIP\)蚯蚓(单调性)。时间复杂度$ \mathcal O(k)$。

我用一个优先队列一个队列水过了。

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define int long long
int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=sum*10+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
signed main(){
    int a=read(),b=read(),k=read();
    if(k==1){
        printf("%lld",a*b-a-b);
        return 0;
    }
    priority_queue<int> q1;
    queue<int> q2;
    q1.push(a*b-a-b-a);
    q2.push(a*b-a-b-b);
    k--;
    int ans;
    while(k--){
        int w1=q1.top();
        int w2=q2.front();
        if(w1<w2){
            ans=w2;
            q2.pop();
            q2.push(w2-b);
            q1.push(w2-a);
        }
        else{
            ans=w1;
            q1.pop();
            q1.push(w1-a);
        }
    }
    printf("%lld",ans);
    return 0;
}

F 寻找宝藏


按颜色考虑,把一种颜色的点去掉之后树会分成不同的连通块,对于一个大小为\(a\)的连通块,这个颜色对于这个连通块中点的答案贡献为\(n-a\)。设颜色数为\(num\)每一个点的答案就是\(n*num-\sum f(i)\),其中\(f(i)\)就是这个点在去掉同一颜色的点后所在连通块大小的和。

然后就是实现上问题。

怎么求连通块大小:\(size(u)-\sum size(v_i)\)

怎么把连通块大小累加到所有在连通块中的点上:树上差分
由于非根节点必然是去掉此节点父亲颜色后一个连通块的公共祖先。
而根节点可能是很多连通块的公共祖先,写代码时要特判。

TLE代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define N 1010000
int cnt,a[N],num,n,pre[N],head[N],size[N],book[N],spe[N];
long long sum[N],f[N];
int read(){
	int sum=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 
	while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
	return sum*f;
}
struct edge{
	int nxt,to;
}e[N*2];
void add_edge(int u,int v){
	cnt++;
	e[cnt].nxt=head[u];
	e[cnt].to=v;
	head[u]=cnt;
}
void dfs1(int u,int fa){
	size[u]=1;
	int tmp=pre[a[u]];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa)continue;
		pre[a[u]]=v;
		dfs1(v,u);
		size[u]+=size[v];
	}
	pre[a[u]]=tmp;
	sum[pre[a[u]]]-=size[u];
	spe[a[u]]-=size[u];
	if(u==1) sum[u]+=(long long)size[u]*num;
	else sum[u]+=size[u];
}
void dfs2(int u,int fa){
	int tmp=pre[a[u]];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa)continue;
		pre[a[u]]=v;
		dfs2(v,u);
	}
	pre[a[u]]=tmp;
	if(u!=1){
		if(pre[a[u]]==1)sum[u]-=spe[a[u]];
		else sum[u]-=sum[pre[a[u]]];
	}
}
void dfs3(int u,int fa,long long tot){
	tot+=sum[u];
	f[u]=tot;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa)continue;
		dfs3(v,u,tot);
	}
	tot-=sum[u];
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		book[a[i]]++;
	}
	for(int i=1;i<=n;i++)num+=(bool)book[i];
	for(int i=1;i<=n-1;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	} 
	for(int i=1;i<=n;i++)pre[a[i]]=1,spe[a[i]]=n;
	dfs1(1,0);
	dfs2(1,0);
	dfs3(1,0,0);
	for(int i=1;i<=n;i++)printf("%lld\n",(long long)n*num-f[i]);
	return 0;
}
posted @ 2021-06-06 12:57  Xu-daxia  阅读(37)  评论(0编辑  收藏  举报