ps:感觉比昨天晚上的华师题目质量高一点,至少emmm,题面没有那么多bug。
下面忽略了水题,只给出我觉得有价值的几个题目的题解和代码:
【C:神山神石】:
题意:
每一天,Wells可以喂神山一个神石,使得神山的高度变为原高度乘上神石的能力值,或者使得所有的神石能力值+1。第 0 天时,所有神石的能力值为 0,神山的高度为 1。Wells想知道神山最快的长成刚好某一个高度值最少需要多少天。输入L,R,P(L,R<1e9; P<=100);求多少个x满足L<=x<=R,最小天数小于P。
思路:
注意是刚好长到x的最小天数,不能超过x。通过暴力搜索,我们发现能够在100天内长到的高度有4e6个左右,把这4e6个离散,离散后是3e6左右个,所以我们应该想办法找到可以长到的高度,然后判断是否在[L,R]区间里,具体的:结果等于加法次数+乘法次数,加法次数等于最大发因子,乘法次数可以用背包求解最优。(充分利用单调性)
(代码做了注释,理解起来不难。)
#include<map> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; const int P[29]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,57,59,61,67,71,73,79,83,89,91,97,101}; const int INF=1000000000; const int maxn=5000000; int num[maxn],Dis[maxn],ans,L,R,p; bool FF[maxn]; void dfs(int now,int x) { if (now>28){ num[++num[0]]=x; return; } while(true){ dfs(now+1,x); if(R/x<P[now]) break; x*=P[now]; } } int main() { cin>>L>>R>>p; dfs(1,1); for (int i=1;i<=num[0];i++) Dis[i]=INF; Dis[1]=0; //cout<<num[0]<<endl; sort(num+1,num+num[0]+1); num[0]=unique(num+1,num+num[0]+1)-(num+1); //cout<<num[0]<<endl; for(int i=2;i<=p;i++){ //枚举加法的天数,即增加神石的能量。 int l=1; for(int j=1;j<=num[0];j++){ while(l<=num[0]&&num[l]<i*num[j]) l++; //得到num[l]==num[j]*i。因为二者dfs都存在,所以第一个大于等于的其实就是等于 (如果不超过R) if(l<=num[0]){ Dis[l]=min(Dis[j]+1,Dis[l]); //Dis是做乘法的天数 if(Dis[l]+i<=p) FF[l]=true; } } } for(int i=1;i<=num[0];i++) if (FF[i]&&num[i]<=R&&num[i]>=L) ans++; printf("%d ",ans); return 0; }
【D:I'm new here】
题意:
给定一棵树(N<1e5),树边上有边权,现在求任意点对的路径异或值的和。
思路:
因为两点之间的异或Xor(u,v)=Xor(root,u)^Xor(root,v)。所以我们DFS得到每个点到根的异或值Xi。然后计算两两异或值。因为异或没有结合性,所以不能用前缀和来计算。正确的打开方式是对每一位sig,我们计算1的个数(cnt)和0的个数(N-cnt),那么这一位的贡献就是cnt*(N-cnt)*(1<<sig)。然后累计即可。(但是比赛的时候(long long位置没有写好),打死也没有找到bug。。。真让人脑壳大)
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; const int maxn=100010; int Laxt[maxn],Next[maxn<<1],To[maxn<<1],val[maxn<<1],cnt; int Xor[maxn],ct[maxn]; ll ans; void add(int u,int v,int w){ Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; val[cnt]=w; } void init() { cnt=0; ans=0; memset(Laxt,0,sizeof(Laxt)); memset(ct,0,sizeof(ct)); } void dfs(int u,int fa) { for(int i=Laxt[u];i;i=Next[i]){ if(To[i]!=fa){ Xor[To[i]]=Xor[u]^val[i]; dfs(To[i],u); } } } int main() { int T,N,i,j; scanf("%d",&T); while(T--){ init(); scanf("%d",&N); for(i=1;i<N;i++){ int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); } dfs(1,0); for(i=1;i<=N;i++) for(j=0;j<32;j++) if(Xor[i]&(1<<j)) ct[j]++; for(j=0;j<32;j++) ans=ans+((ll)ct[j]*(N-ct[j]))*(1LL<<j); cout<<ans<<endl; } return 0; }
【E:EZ's binoculars】
题意:
给你一些点(N<1e5),然后其实是每次询问给你一个中心在( x , y )的四边相等的菱形,对角线长为d,求问多少点在菱形里。
官方题解:
首先可以想到二维树状数组维护。但是显然内存不允许,时间复杂度也不允许。 所以需要离线优化 那么考虑优化,可以基于离线排序后优化。现将所有的点P按照( x + y )关键字排序,然后将菱形的四个点按照( x + y )排序。 那么,每次如果P的(x+y)<= 菱形的(x+y),那么将p的(y-x)离散化后的值插入到树状数组 那么查询菱形中一点 t 下方有多少个点时,就是查询小于等于t.y - t.x的点的个数,树状数组查询即可(必须离散化后查询)。 所以时间复杂度就是O(nlgn)。
(实现也不那么容易)
(占位)
【F:Lunch War with the Donkey】
题意:
现在有N瓶牛奶和M个面包,现在要求搭配成min(N,M)队,每一队有一瓶牛奶和一个面包,价值是a牛奶*b面包,求最大和最小。
思路:
排序,最大值的话,显然是从大到小前面min(N,M)个分别相乘;最小的话,显然是后面min(N,M)个,但是注意要交错相乘(排序不等式)。
#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; const int maxn=100010; ll a[maxn],b[maxn],ans1,ans2; int main() { int T,N,M,i; while(~scanf("%d%d",&N,&M)) { ans1=ans2=0; for(i=1;i<=N;i++) scanf("%d",&a[i]); for(i=1;i<=M;i++) scanf("%d",&b[i]); sort(a+1,a+N+1); sort(b+1,b+M+1); for(i=1;i<=min(N,M);i++) ans1+=a[N+1-i]*b[M+1-i]; N=min(N,M); for(i=1;i<=N;i++) ans2+=a[i]*b[N+1-i]; cout<<ans1<<" "<<ans2<<endl; } return 0; } /********************************************************************** Problem: 2084 User: nimphy Language: C++ Result: AC Time:1124 ms Memory:3584 kb **********************************************************************/
【J:Pigs can't take a sudden turn】
题意:
两个点以两个恒定的速度移动,问过程中两点的最短距离。
思路:
可以三分;可以用二次函数不等式(特判相遇的情况); 也可以:
用相对运算,假设一个点不动,则另一个点的相对运动轨迹就是一条线段,然后求点到线段最短距离。
#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; int x1,yy1,x2,y2,u1,v1,u2,v2; double ans; int main() { int T,Case=0; cin>>T; while(T--){ cin>>x1>>yy1>>x2>>y2>>u1>>v1>>u2>>v2; u1-=u2; v1-=v2; if(u1==0&&v1==0) { //相对速度为0 ans=(x1-x2)*(x1-x2)+(yy1-y2)*(yy1-y2); ans=sqrt(ans); } else if(x1==x2&&yy1==y2) ans=0.0; //开始就重合 else { ans=(x1-x2)*(x1-x2)+(yy1-y2)*(yy1-y2); ans=sqrt(ans); if((x2-x1)*u1+(y2-yy1)*v1>=0) //如果小于等于90度:点到直线的距离 ans=abs(-v1*x2+u1*y2+v1*x1-yy1*u1)/sqrt(u1*u1+v1*v1); } printf("Case %d: %.6lf ",++Case,ans); } return 0; }