2020牛客暑期多校训练营(第二场)
根据难度排序。
个人总结向。
如果有什么讲的不清楚的欢迎留言私信交流~
D. Duration(签到)
题意: 给出两个时分秒表示的时间,问相差多少秒。
思路:
化成秒相减就可以了。
代码:
#include<bits/stdc++.h> #define pb push_back #define ld long double #define ll long long #define ull unsigned long long #define fi first #define se second #define inf 0x3f3f3f3f #define endl "\n" #define pi acos(-1.0) #define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int manx=1e6+5; const int N=500+5; const int mod=1e9+7; const int mo=1e8; ll h1,h2,m1,m2,s1,s2; int main(){ string a,b; cin>>a>>b; ll cnt=0; for(int i=0;i<a.size();i++){ if(a[i]==':') cnt++; if(cnt==0)h1=h1*10+(a[i]-'0'); else if(cnt==1) m1=m1*10+(a[i]-'0'); else s1=s1*10+(a[i]-'0'); } cnt=0; for(int i=0;i<b.size();i++){ if(b[i]==':') cnt++; if(cnt==0)h2=h2*10+(b[i]-'0'); else if(cnt==1) m2=m2*10+(b[i]-'0'); else s2=s2*10+(b[i]-'0'); } s1=s1+m1*60+h1*3600; s2=s2+m2*60+h2*3600; cout<<abs(s1-s2)<<endl; return 0; }
F. Fake Maxpooling(单调队列)
题意: n∗m的矩阵中每一位为gcd(i,j),求所有k∗k的子矩阵中最大值的和。
思路:
用单调队列 / 滑动窗口 维护 二维的窗口最大值。
其实和一维单调队列差不多,先对原矩阵 a 每一行求出长度为 k 的(横向)窗口的最大值,得到 ma 矩阵。
然后第二次枚举的元素不再是原本的矩阵,而是 ma 矩阵。
对 ma 矩阵每一列求出长度为 k 的(竖向)窗口的最大值,覆盖在原 ma 矩阵的位置,这里选择覆盖是因为这题对内存要求较高。
特别注意的点是求 ,可以记忆化一下,否则有超时的可能。
还有切记数组开 int 就够了,统计最大值之和开 ll 即可。
代码:
#include<bits/stdc++.h> #define mp make_pair #define pb push_back #define ll long long #define fi first #define se second #define inf 0x3f3f3f3f #define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int manx=5001; const int N=1e3+5; const int mod=1e9+7; int a[manx][manx],ma[manx][manx],q[manx]; ll ans,head,tail,n,m,k; int main() { scanf("%lld%lld%lld",&n,&m,&k); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(!a[i][j]) a[i][j]=a[j][i]=i/__gcd(i,j)*j; for(int i=1;i<=n;i++){ head=1,tail=0; for(int j=1;j<=m;j++){ while(head<=tail&&q[head]<(j-k+1)) ++head; while(head<=tail&&a[i][q[tail]]<=a[i][j]) --tail; q[++tail]=j; if(j>=k) ma[i][j]=a[i][q[head]]; } } for(int i=k;i<=m;i++){ head=1,tail=0; for(int j=1;j<=n;j++){ while(head<=tail&&q[head]<(j-k+1)) ++head; while(head<=tail&&ma[q[tail]][i]<=ma[j][i]) --tail; q[++tail]=j; if(i>=k) ma[j][i]=ma[q[head]][i]; } } for(int i=k;i<=n;i++) for(int j=k;j<=m;j++) ans+=ma[i][j]; printf("%lld",ans); return 0; }
C. Cover the Tree(dfs序+思维)
题意: 选最少数量的链来覆盖整颗树,使树的每条边都至少被覆盖一次。
思路:比赛的时候是标记路径,贪心的选择叶子,也就是乱搞
正解是 dfs 序, 然后一对一对的输出叶子,注意叶子数目为奇数时,需将剩下的叶子与根节点连接为一条路径。
代码:
#include<bits/stdc++.h> #define mp make_pair #define pb push_back #define ll long long #define fi first #define se second #define inf 0x3f3f3f3f #define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) using namespace std; const int manx=1e6+5; const int N=1e3+5; const int mod=1e9+7; vector<ll>g[manx]; ll ans[manx]; ll n,m,k; void dfs(ll u,ll pre){ if(g[u].size()==1) ans[++ans[0]]=u; for(auto v:g[u]){ if(v==pre) continue; dfs(v,u); } } int main() { io; cin>>n; for(int i=1;i<n;i++){ ll u,v; cin>>u>>v; g[u].pb(v); g[v].pb(u); } dfs(1,-1); m=(ans[0]+1)/2; cout<<m<<endl; for(int i=1;i*2<=ans[0];i++) cout<<ans[i]<<" "<<ans[i+m]<<endl; if(ans[0]&1) cout<<ans[m]<<" "<<1<<endl; return 0; }
B. Boundary(计算几何)
题意: 在平面上给若干个点,求一个过原点的圆,使得尽量多的点在圆上。
思路:
三点确定一个圆,由于过原点,所以只需要 枚举即可,用 map 存半径进行统计。
精度的话 eps 设置 比较好。
最后输出时不要忘了加上原点。
代码:
#include<bits/stdc++.h> #define mp make_pair #define pb push_back #define ll long long #define fi first #define se second #define inf 0x3f3f3f3f #define eps 1e-10 using namespace std; const int manx=1e6+5; const int N=5e3+5; const int mod=1e9+7; struct Point{ double x,y; Point(double x,double y){ this->x=x; this->y=y; } Point(){ x=y=0; } }; Point p[N]; Point xin(Point a,Point b,Point c) { double a1 = b.x - a.x, b1 = b.y - a.y, c1 = (a1*a1 + b1*b1)/2; double a2 = c.x - a.x, b2 = c.y - a.y, c2 = (a2*a2 + b2*b2)/2; double d = a1*b2 - a2*b1; return Point(a.x + (c1*b2 - c2*b1)/d, a.y + (a1*c2 -a2*c1)/d); } map<pair<double,double> ,ll>mps; int main(){ ll n; scanf("%lld",&n); ll k=0; Point zero(0,0); for(int i=1;i<=n;i++) scanf("%lf%lf",&p[i].x,&p[i].y); ll ans=0; for(int i=1;i<=n;i++){ mps.clear(); for(int j=i+1;j<=n;j++){ if(fabs(p[i].x*p[j].y-p[i].y*p[j].x)>eps){ Point r=xin(p[i],p[j],zero); ans=max(ans,++mps[mp(r.x,r.y)]); } } } printf("%lld\n",++ans); return 0; }
J. Just Shuffle(置换群)
题意: 给一个长度为n的1−n的排列,开始为1,2,3,……,n,给出经过k次相同置换后得到的排列,问置换一次后的排列。
思路:
置换群的符合运算:
对于,两边乘一个 k 的逆元 inv,有
,化简得到:
因为k为大质数,所以在置换时循环大小不会变化。
对于每一个环单独考虑,给定排列的环大小就是置换的循环大小,将 当做一次置换,那么只要找到
时的 x ,也就是 k 的逆元,做 x 次置换,就可以得到答案。
注意给出的是置换完的样子,而不是置换。
代码:
#include<bits/stdc++.h> #define mp make_pair #define pb push_back #define ll long long #define fi first #define se second #define inf 0x3f3f3f3f #define eps 1e-10 using namespace std; const int manx=1e6+5; const int N=5e3+5; const int mod=1e9+7; ll exgcd(ll a,ll b,ll &x,ll &y){ if(b==0){ x=1,y=0; return a;} ll gcd=exgcd(b,a%b,y,x); y-=a/b*x; return gcd; } ll inv(ll a,ll b,ll &x,ll &y){ ll gcd=exgcd(a,b,x,y); if(gcd!=1) return -1; else return (x+b)%b; } ll a[manx],vis[manx],ans[manx]; vector<ll>g; int main(){ ll n,k; cin>>n>>k; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++){ if(vis[i]) continue; vis[i]=1; g.clear(); g.pb(i); ll x=a[i],y; while(x!=i) g.pb(x),vis[x]=1,x=a[x]; if(g.size()==1){ans[i]=i; continue;} x=inv(k%g.size(),g.size(),x,y); for(int j=0;j<g.size();j++) ans[g[j]]=g[(j+x)%g.size()]; } for(int i=1;i<=n;i++) cout<<ans[i]<<" "; return 0; }
A. All with Pairs(KMP+Hash)
题意: 给出若干个字符串,定义 为串
的前缀与串
的后缀的最大重合长度。
求 。
思路:
对每个字符串的后缀进行hash,用map计数。
然后枚举每个字符串,枚举的时候计算前缀hash值,map中相同hash值的个数就是与其相匹配的后缀个数。
特别注意的是,题目要求的是最大的重合长度,那么如何取消短的前缀造成的重复计算?
这个时候可以想到 数组,
即可回滚长度,也就是说明有 子串
与 子串
有相同的前缀,这个时候就用
,用可回滚处的答案减去较长子串对答案的贡献。
最后还有一个坑点就是 数组是对于字符串来说整体向右移动了一位,所以部分地方枚举或者计算答案需要注意下标。
代码:
#include<bits/stdc++.h> #define mp make_pair #define pb push_back #define ll long long #define ull unsigned long long #define fi first #define se second #define inf 0x3f3f3f3f #define eps 1e-10 using namespace std; const int manx=1e6+5; const int N=5e3+5; //const int mod=1e9+7; const int mod=998244353; const ull mo=233; string s[manx]; ll nexts[manx],ans[manx]; map<ull,ll>cnt; void dohash(string s){ ll n=s.size(); ull sum=0,base=1; for(int i=n-1;i>=0;i--){ sum+=(s[i]-'a'+1)*base; base*=mo; cnt[sum]++; } } void donext(string p){ ll i=0,j=-1,m=p.size(); nexts[i]=j; while(i<m){ if(j==-1||p[i]==p[j]) nexts[++i]=++j; else j=nexts[j]; } } int main(){ ll n; cin>>n; ll x=0; for(int i=1;i<=n;i++) cin>>s[i],dohash(s[i]); for(int i=1;i<=n;i++){ donext(s[i]); ull sum=0; for(int j=0;j<s[i].size();j++){ sum=sum*mo+(s[i][j]-'a'+1); ans[j+1]=cnt[sum]; } for(int j=1;j<=s[i].size();j++) if(nexts[j]>0) ans[nexts[j]]-=ans[j]; for(int j=1;j<=s[i].size();j++) x+=ans[j]%mod*j%mod*j%mod,x%=mod; } cout<<x<<endl; return 0; }