成电体验营
最大疯子树
给定一棵 n 个结点的树,结点编号为 1~n,i 号结点的权重记为 wi(每个点的权值各不相同)。我们定义一个“疯子树”为:
1. 是一个联通子图。
2. 我们将子图内的点按照权重从小到大排序后序列为 b1,b2,…,bm,对于任意的 i(i<m),bi到 bi+1最短路径(不含 bi和 bi+1)上的结点的权值都小于等于 i wb 。
输出包含结点最多的“疯子树”的结点数。
n≤200000,0 < wi≤100000000
题解:
简单的树dp。
问题转化:
我们考虑按照权值从小到大把点加入,如果当前这个点$i$可以加入我们之前的一个合法的联通子图$G$,那么$G$一定和$i$有一条边直接联通。
因为$G$里面的点的权值都比i的权值小,所以如果我们把边从无向改为有向,由权值大的点指向权值小的点,那么每个点最多有一个出边,是把这个点加入$G$时和$G$直接相连的那条边,而且后面加入$G$的点如果有向它连的边,那么都是指向它的,是它的入边。
那么问题转化为,给你一个由有向边构成的树,让你找到一个最大的联通子图使得每个点的出度不超过1。
解法:
树dp。随便找个点当做根,$dp[i][0/1]$表示$i$这个点向儿子有$0/1$条出边所能形成的最大联通图。
如果$x$是i的儿子,那么
当$w[x]>w[i]$时,$dp[i][0] += dp[x][0]$ (x连向i)
当$w[x]<w[i]$时,$dp[i][1]=max(dp[x][1]) + dp[i][0]$ (i连向x)
一定有$dp[i][1]>dp[i][0]$,$ans=max(dp[i][1])$
时间复杂度和空间复杂度均为$O(n)$。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) const int maxn=2e5+7; int n,w[maxn],dp[maxn][2],ans; ll ff;char cc; template<typename T> void read(T& aa) { aa=0; ff=1; cc=getchar(); while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int fir[maxn],nxt[2*maxn],to[2*maxn],e=0; void add(int x,int y) { to[++e]=y;nxt[e]=fir[x];fir[x]=e; to[++e]=x;nxt[e]=fir[y];fir[y]=e; } void dfs(int pos,int f) { int y,z; dp[pos][0]=1; dp[pos][1]=0; for(y=fir[pos];y;y=nxt[y]) { if((z=to[y])==f) continue; dfs(z,pos); if(w[z]>w[pos]) dp[pos][0]+=dp[z][0];//pos <- z else dp[pos][1]=max(dp[pos][1],dp[z][1]);//pos -> z } dp[pos][1]+=dp[pos][0]; ans=max(ans,dp[pos][1]); } int main() { freopen("crazy.in","r",stdin); freopen("crazy.out","w",stdout); int x,y; while(scanf("%d",&n)==1) { For(i,1,n) fir[i]=0; ans=0; For(i,1,n) read(w[i]); For(i,1,n-1) { read(x); read(y); add(x,y); } dfs(1,0); printf("%d\n",ans); } fclose(stdin); fclose(stdout); return 0; }
分解
给定正整数 N,你需要将其分解为若干正整数的乘积,要求分解出的数之间 相差都不超过 1。
N≤10^18,T≤10
题解:
如果分解成的正整数是x和x+1
Pollard_rho分解,对于一个质数p_i,如果N中含有a_i个p_i,那么要么x^r包含a_i个p_i,要么(x+1)^t包含,状压枚举x中包含哪些质数
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<map> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) const int TM=25,maxn=1007; const ll INF=2e18; ll Td,tot,ans; map<ll,ll> G; map<ll,ll>::iterator it; ll zz[maxn],num[maxn]; ll ff;char cc; template<typename T> void read(T& aa) { aa=0; ff=1; cc=getchar(); while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } ll gcd(ll x,ll y) { return y==0? x:gcd(y,x%y); } ll qc(ll x,ll k,ll mod) { ll rs=0; while(k) { if(k&1) rs=(rs+x)%mod; k>>=1; x=(x+x)%mod; } return rs; } ll qp(ll x,ll k,ll mod) { ll rs=1; while(k) { if(k&1) rs=qc(rs,x,mod); k>>=1; x=qc(x,x,mod); } return rs; } bool isprime(ll n) { if(n==2||n==3||n==5||n==7) return 1; if(n<2||(n%6!=1&&n%6!=5)) return 0; ll m=n-1,t=0,x,y; while((m&1)==0) t++,m>>=1; For(qaq,1,TM) { x=rand()%(n-2)+2; x=qp(x,m,n); For(i,1,t) { y=qc(x,x,n); if(y==1&&x!=1&&x!=n-1) return 0; x=y; } if(y!=1) return 0; } return 1; } ll prho(ll n,ll c) { ll x=rand()*rand()%n+1,y=x,k=2,i=0,d; while(1) { x=(qc(x,x,n)+c)%n+2; d=gcd((y-x+n)%n,n); if(d>1&&d<n) return d; if(x==y) return n; if((++i)==k) { k<<=1; y=x; } } return n; } void find(ll n,ll m) { if(isprime(n)) { ++G[n]; return; } ll k=m,p=n; while(p>=n&&m) p=prho(n,m--); find(p,k); find(n/p,k); } struct Node{ ll x,num1,num2; Node(){} Node(ll x,ll num1,ll num2):x(x),num1(num1),num2(num2){} bool operator < (const Node& b) const{return x<b.x;} }node[maxn*maxn]; ll p[maxn]; void get_ans(ll s) { ll gcd1=0,gcd2=0; For(i,1,tot) if((s>>(i-1))&1){ if(!gcd1) gcd1=num[i]; else gcd1=gcd(gcd1,num[i]); } else { if(!gcd2) gcd2=num[i]; else gcd2=gcd(gcd2,num[i]); } ll x=1,y=1,num1=1,num2=1,o=0; For(i,1,tot) if((s>>(i-1))&1) num1*=qp(zz[i],num[i]/gcd1,INF); For(i,1,tot) if((s>>(i-1)&1)==0) num2*=qp(zz[i],num[i]/gcd2,INF); for(it=G.begin();it!=G.end();++it) { p[++o]=it->first; num[o]=it->second; } For(r,1,gcd1) if(gcd1%r==0) { x=qp(num1,gcd1/r,INF); For(t,1,gcd2) if(gcd2%t==0) { y=qp(num2,gcd2/t,INF); if(y!=x+1) continue; if(y<x) break; node[++ans]=Node(x,r,t); } } } void print_ans(int p) { printf("%lld ",node[p].num1+node[p].num2); For(i,1,node[p].num1) printf("%lld ",node[p].x); For(i,1,node[p].num2) printf("%lld ",node[p].x+1); printf("\n"); } int main() { freopen("little.in","r",stdin); freopen("little.out","w",stdout); ll n,sz,x; read(Td); while(Td--) { read(n); if(n==(n&-n)) { printf("-1\n"); continue; } G.clear(); tot=ans=0; find(n,120); for(it=G.begin();it!=G.end();++it) { zz[++tot]=it->first; num[tot]=it->second; } if(tot<2) { For(i,1,num[1]) if(num[1]%i==0) ++ans; printf("%lld\n",ans); For(i,1,num[1]) if(num[1]%i==0) { x=qp(zz[1],i,INF); printf("%lld ",num[1]/i); For(j,1,num[1]/i) printf("%lld ",x); printf("\n"); } continue; } sz=1<<tot; ll gcd1=num[1]; For(i,1,tot) gcd1=gcd(gcd1,num[i]); For(r,1,gcd1) if(gcd1%r==0) { x=1; For(i,1,tot) x*=qp(zz[i],num[i]/r,INF); node[++ans]=Node(x,r,0); } for(int s=1;s<sz-1;++s) get_ans(s); printf("%lld\n",ans); sort(node+1,node+ans+1); For(i,1,ans) print_ans(i); } fclose(stdin); fclose(stdout); return 0; }
蛋糕
今天是鲍勃的生日,爱丽丝打算做一个蛋糕送给他。 这是鲍勃的 n 岁生日,所以爱丽丝的蛋糕必须是正 n 边形。
而且,鲍勃很喜 欢数字 m,所以这个蛋糕必须放在一个正 m 边形的盒子里。
为了让气氛更加浪漫, 爱丽丝将在蛋糕的中心插上一根蜡烛,显然,蜡烛既在蛋糕的中心,又在盒子的 中心是最好的。
换句话说,爱丽丝应该使正 n 边形的蛋糕能被容纳在正 m 边形的盒子里,且 使其中心重合。
事实上,爱丽丝已经做好了蛋糕,蛋糕是边长为 1 的正 n 边形, 现在她想知道,正 m 边形盒子的最小边长是多少。
n,m≤1000000000
题解:
先在n边形外面套一个lcm(n,m)边形,再在lcm(n,m)边形外面套m边形
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db long double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) const db PI=acos(-1); db n,m,p; char cc; ll ff; template<typename T>void read(T& aa) { aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } ll gcd(ll x,ll y) {return y==0? x:gcd(y,x%y);} int main() { freopen("cake.in","r",stdin); freopen("cake.out","w",stdout); while(scanf("%Lf%Lf",&n,&m)==2) { p=n/gcd(n,m)*m; printf("%.4Lf\n",cos(PI/p)*tan(PI/m)/sin(PI/n)); } return 0; }
Chika 的烦恼
Chika 家经营着一家旅馆,她经常要帮家里的忙,有时候,她需要去清理长 满杂草的后院。
具体地,后院里面的每棵草都有其生长速率(每天所能生长的高 度),在某些给定的日期,她需要把高度大于某个值 b 的杂草割掉一截,使其高 度剩余 b。
(b 在每次给定的日期时不一定相同。)
她想请你帮她统计一下,每次她所割掉的草的总量是多少。
假设最开始每棵草的高度是零。
1≤n,m≤400000, 1≤ai≤10^6 , 1≤di≤10^12 ,0≤bi≤10^12,d1<d2<...<dm,保证在每次割草时,没有草的高度会超过 2×10^12
题解:
由于任何时候草的高度的大小关系一定是和a大小关系是相同的
所以对草根据a排序,每次被割的草一定是a比较大的一坨,直接在线段树上二分找到这一坨草割掉就可以了
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) const int maxn=4e5+7; ll n,m,a[maxn],b[maxn]; ll ff;char cc; template<typename T> void read(T& aa) { aa=0; ff=1; cc=getchar(); while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } bool cmp(const ll x,const ll y) { return x>y; } ll sum[4*maxn],maxnum[4*maxn],laz[4*maxn],laz2[4*maxn],ql,qr,qx; int pd(int pos,ll l,ll r) { ll mid=(l+r)>>1; if((laz[pos]==-1&&laz2[pos]==0)||l==r) { laz[pos]=-1; laz2[pos]=0; return mid; } if(laz[pos]!=-1) { laz[pos<<1]=laz[pos<<1|1]=laz[pos]; laz2[pos<<1]=laz2[pos<<1|1]=0; sum[pos<<1]=(mid-l+1)*laz[pos]; sum[pos<<1|1]=(r-mid)*laz[pos]; maxnum[pos<<1]=maxnum[pos<<1|1]=laz[pos];// } if(laz2[pos]) { laz2[pos<<1]+=laz2[pos]; laz2[pos<<1|1]+=laz2[pos]; sum[pos<<1]+=(b[mid]-b[l-1])*laz2[pos]; sum[pos<<1|1]+=(b[r]-b[mid])*laz2[pos]; maxnum[pos<<1]+=laz2[pos]*a[l];// maxnum[pos<<1|1]+=laz2[pos]*a[mid+1];// } laz[pos]=-1; laz2[pos]=0; return mid; } void chge(int pos,ll l,ll r) { int mid=pd(pos,l,r); if(l>=ql&&r<=qr) { laz[pos]=maxnum[pos]=qx; laz2[pos]=0; sum[pos]=(r-l+1)*qx; return ; } if(ql<=mid) chge(pos<<1,l,mid); if(qr>mid) chge(pos<<1|1,mid+1,r); sum[pos]=sum[pos<<1]+sum[pos<<1|1]; maxnum[pos]=max(maxnum[pos<<1],maxnum[pos<<1|1]); } ll q(int pos,int l,int r) { int mid=pd(pos,l,r); if(l>=ql&&r<=qr) return sum[pos]; ll rs=0; if(ql<=mid) rs+=q(pos<<1,l,mid); if(qr>mid) rs+=q(pos<<1|1,mid+1,r); return rs; } ll get_ans(int pos,int l,int r,ll x) { int mid=pd(pos,l,r); if(l==r) return l; if(maxnum[pos<<1|1]>x) return get_ans(pos<<1|1,mid+1,r,x); else return get_ans(pos<<1,l,mid,x); } int ef(ll x) { ql=qr=1; if(q(1,1,n)<=x) return 0; ql=qr=n; if(q(1,1,n)>x) return n; return get_ans(1,1,n,x); } int main() { freopen("grass.in","r",stdin); freopen("grass.out","w",stdout); read(n); read(m); memset(laz,-1,sizeof(laz)); For(i,1,n) read(a[i]); sort(a+1,a+n+1,cmp); For(i,1,n) b[i]=b[i-1]+a[i]; ll x,y,pos,last=0,tot; For(i,1,m) { read(x); read(y); qx=x-last; pd(1,1,n); sum[1]+=qx*b[n]; laz2[1]+=qx; maxnum[1]+=qx*a[1]; tot=sum[1]; if((pos=ef(y))!=0) { ql=1; qr=pos; qx=y; chge(1,1,n); } printf("%lld\n",tot-sum[1]); last=x; } fclose(stdin); fclose(stdout); return 0; } /* 4 4 1 2 4 3 1 1 2 2 3 0 4 4 */
总结:
考场上真想骂自己是个sb。
t2,Pollard_rho板子写错了总是调不出来,最后写了一个暴力的分解大数。
t3以前饺学长讲过,还有点印象,记得和什么gcd和lcm有关,但是还是做不出来爆零了。
弃了t3之后,在最后一个小时才开始做t4。t1和t2对拍的时间都没有。
考完了觉得自己确实是个sb。
t4一个这么无聊的线段树,竟然认为先二分再在线段树里面查,两个log能过,结果被卡成60。
t2自己作死把输出的格式写错了,暴力分都没拿到,成功挂成10分。
这么容易AK的一套题竟然连暴力分都没拿够。
主要是这几个问题:
1、板子不够熟练
2、数据结构没有用熟
3、数学和计算几何辣鸡
4、考场上做题容易受到干扰,注意力不集中