牛客练习赛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
");
else printf("No
");
}
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
",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*lfloorfrac{i}{j}
floor) 。
而(lfloorfrac{i}{j}
floor)可以联想到整除分块。
可以记录一个前缀和数组(sum[i][j])代表以第(i)个数为结尾两个数之间间隔(j)的(f)的前缀和。
只记录(lfloorfrac{i}{j}
floor<=sqrt(n))时的前缀和。
对于每一个(i)。
对于(lfloorfrac{i}{j}
floor<=sqrt(i))的部分用前缀和求解。
对于(lfloorfrac{i}{j}
floor>sqrt(n))的部分可以证明满足这种条件的j不超过sqrt(i)个,可直接累加。
复杂度(O(nsqrt[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 inmathbb{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
",(long long)n*num-f[i]);
return 0;
}