2020牛客暑期多校训练营(第二场)解题报告
D
温暖的签到题。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll input(){
ll x=0,f=0;char ch=getchar();
while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return f? -x:x;
}
ll h1,m1,s1;
ll h2,m2,s2;
int main(){
h1=input(),m1=input(),s1=input();
h2=input(),m2=input(),s2=input();
ll t2=h2*3600+m2*60+s2;
ll t1=h1*3600+m1*60+s1;
printf("%lld\n",abs(t2-t1));
}
C
构造题。基本原理与这里的K现当。一个结论是答案一定是叶子节点的一半,一个可行的方案一定是不同子树的叶子节点互相连接。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll input(){
ll x=0,f=0;char ch=getchar();
while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return f? -x:x;
}
#define PII pair <int,int>
#define fr first
#define sc second
#define mp make_pair
#define pb push_back
const int N=4e5+7;
vector<int> G[N];
int cnt[N];
int n,h;
vector<int> DFS;
vector<PII> Ans;
void dfs(int u,int fa){
if(cnt[u]==1) DFS.pb(u);
for(auto v:G[u]){
if(v==fa) continue;
dfs(v,u);
}
}
int main(){
n=input();
for(int i=1;i<n;i++){
int u=input(),v=input();
G[u].pb(v),G[v].pb(u);
cnt[u]++,cnt[v]++;
}
dfs(1,0);
int t=DFS.size();
for(int i=0;i<t/2;i++){
Ans.pb(mp(DFS[i],DFS[i+t/2]));
}
if(t%2) Ans.pb(mp(DFS[t/2-1],DFS[t-1]));
printf("%d\n",Ans.size());
for(auto v:Ans){
printf("%d %d\n",v.fr,v.sc);
}
}
F
跟队友在这个题互相演😂。
裸的二维区间最值的题目,我们首先使用单调队列求出矩阵中每个元素向右延伸\(K\)位的最大值\(t_{ij}\),接着再次使用单调队列求出每个\(t_{ij}\)向下延伸\(K\)位的最大值,即可求得所有的二维区间最值。对于构造出\(lcm(i,j)\)矩阵,我们可以考虑记忆化来降低复杂度,避免超时。
#include <bits/stdc++.h>
using namespace std;
#define N 5010
#define ll long long
int h[N][N], max1d[N][N];
int n, m, k;
ll solve(int a,int b){
int tmp[N];
for(int j=1;j<=m;j++){
int l=1,r=0;
for(int i=1;i<a;i++){
while(r>=l&&h[tmp[r]][j]<=h[i][j]) r--;
tmp[++r]=i;
}
for(int i=1;i+a-1<=n;i++){
while(r>=l&&h[tmp[r]][j]<=h[i+a-1][j]) r--;
tmp[++r]=i+a-1;
while(tmp[l]<i) l++;
max1d[i][j]=h[tmp[l]][j];
}
}
ll ans=0;
for (int i=1;i+a-1<=n;i++) {
int l=1,r=0;
for (int j=1;j<b;j++) {
while (r>=l&&max1d[i][tmp[r]]<=max1d[i][j]) r--;
tmp[++r]=j;
}
for (int j=1;j+b-1<=m;j++) {
while(r>=l&&max1d[i][tmp[r]]<=max1d[i][j+b-1]) r--;
tmp[++r]=j+b-1;
while(tmp[l]<j) l++;
ll tmax=max1d[i][tmp[l]],ii=i+a-1,jj=j+b-1;
ans+=tmax;
}
}
return ans;
}
int lcm(int i,int j){
return 1LL*i*j/__gcd(i,j);
}
int main() {
scanf("%d%d%d", &n, &m, &k);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(j<=n&&i<=m&&h[j][i]) h[i][j]=h[j][i];
else h[i][j]=lcm(i,j);
}
}
printf("%lld\n",solve(k,k));
return 0;
}
A
神仙队友做的。我贡献了hash_map😁。
对于本题我们不妨枚举所有串的后缀,用map保存某一后缀的个数,由于直接用string类型会超时,所以我们计算hash值保存,然后我们通过枚举字符串的前缀,我们可以通过map知道有多少个后缀跟前缀相同,那么对答案的贡献是\(len^2*cnt_{与该前缀相等的后缀}\),但是这样算出来的答案是不正确的,因为一个前缀对答案不知贡献一次(例如前缀"aba"和后缀"ba"再"a"和“ab"会分别计算一次答案),考虑去重,我们可以利用kmp的next数组从前往后计算\(cnt_{next[j]}-=cnt_j\),之后便可直接计算答案了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll input(){
ll x=0,f=0;char ch=getchar();
while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return f? -x:x;
}
#define PII pair <ll,ll>
#define fr first
#define sc second
#define mp make_pair
const int N=1e6+7;
ll base[2]={19260817,(int)1e5+7},mod[2]={(int)1e9+7,998244353};
ll p[2][N],h[2][N];
void init(){
p[0][0]=p[1][0]=1;
for(int i=1;i<N;i++){
p[0][i]=p[0][i-1]*base[0]%mod[0];
p[1][i]=p[1][i-1]*base[1]%mod[1];
}
}
void str_hash(string s){
h[0][0]=s[0]%mod[0];
h[1][0]=s[0]%mod[1];
for(int i=1;i<s.length();i++){
h[0][i]=(h[0][i-1]*base[0]+s[i])%mod[0];
h[1][i]=(h[1][i-1]*base[1]+s[i])%mod[1];
}
}
PII gethash(int l,int r){
return mp(((h[0][r]-h[0][l-1]*p[0][r-l+1])%mod[0]+mod[0])%mod[0],((h[1][r]-h[1][l-1]*p[1][r-l+1])%mod[1]+mod[1])%mod[1]);
}
const ll mod1=1e9+7;
const int maxsz=3e6+7;
template<typename key,typename val>
class hash_map{public:
struct node{key u;val v;int next;};
vector<node> e;
int head[maxsz],nume,numk,id[maxsz];
int geths(PII &u){
int x=(1ll*u.fr*mod1+u.sc)%maxsz;
if(x<0) return x+maxsz;
return x;
}
val& operator[](key u){
int hs=geths(u);
for(int i=head[hs];i;i=e[i].next)if(e[i].u==u) return e[i].v;
if(!head[hs])id[++numk]=hs;
if(++nume>=e.size())e.resize(nume<<1);
return e[nume]=(node){u,0,head[hs]},head[hs]=nume,e[nume].v;
}
void clear(){
for(int i=0;i<=numk;i++) head[id[i]]=0;
numk=nume=0;
}
};
hash_map<PII,ll> ap;
ll nxt[N],vis[N];
void getNext(string s){
nxt[0]=-1;
for(int i=0,j=-1;i<s.length();){
if(j==-1||s[i]==s[j]) nxt[++i]=++j;
else j=nxt[j];
}
}
string s[N];
int n;
int main(){
init();
n=input();
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=1;i<=n;i++){
str_hash(s[i]);
PII tmp;
for(int j=0;j<s[i].length();j++){
tmp=gethash(j,s[i].length()-1);
ap[tmp]++;
}
}
ll Ans=0;
for(int i=1;i<=n;i++){
str_hash(s[i]);
getNext(s[i]);
for(int j=0;j<=s[i].length();j++) vis[j]=0;
for(int j=s[i].length();j>=1;j--){
PII tmp=gethash(0,j-1);
Ans=(Ans+(ap[tmp]-vis[j]+mod[1])%mod[1]*j%mod[1]*j%mod[1])%mod[1];
vis[nxt[j]]=(vis[nxt[j]]+ap[tmp])%mod[1];
}
}
cout<<Ans<<endl;
}
B
简单计算几何。比赛的时候没有看到,赛后读完题就秒了。首先我们知道(0,0)一定在圆上,那么我们只需要枚举一个点,然后再枚举另一个点,计算有多少个圆心,找到被覆盖最多的圆心就是答案了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll input(){
ll x=0,f=0;char ch=getchar();
while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return f? -x:x;
}
#define PII pair <double,double>
#define fr first
#define sc second
#define mp make_pair
const int N=2007;
PII tcircle(PII A,PII B,PII C){
double x1=A.fr,x2=B.fr,x3=C.fr;
double y1=A.sc,y2=B.sc,y3=C.sc;
double a = x1 - x2;
double b = y1 - y2;
double c = x1 - x3;
double d = y1 - y3;
double e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2))/2.0;
double f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3))/2.0;
double det = b * c - a * d;
if(fabs(det)<1e-5){
return mp(0,0);
}
double x0=-(d*e-b*f)/det;
double y0=-(a*f-c*e)/det;
return mp(x0,y0);
}
int n;
PII p[N];
vector <PII> v;
bool cmp(PII a,PII b){
if(fabs(a.fr-b.fr)<1e-10&&fabs(a.sc-b.sc)<1e-10) return 1;
else return 0;
}
int main(){
n=input();
int Ans=0;
for(int i=1;i<=n;i++){
p[i].fr=input(),p[i].sc=input();
}
for(int i=1;i<=n;i++){
v.clear();
for(int j=1;j<=n;j++){
if(i==j) continue;
PII tmp=tcircle(p[i],p[j],mp(0,0));
if(tmp==mp(0.0,0.0)) continue;
v.push_back(tmp);
}
if(v.empty()){printf("1\n");return 0;}
sort(v.begin(),v.end());
int cnt=1;
for(int j=1;j<v.size();j++){
if(cmp(v[j],v[j-1])) cnt++;
else{
Ans=max(Ans,cnt+1);
cnt=1;
}
}
Ans=max(Ans,cnt+1);
}
printf("%d\n",Ans);
}
J
置换群整数幂运算。题目要求对于一个置换\(T\)我们已知\(T^k\),我们要求\(T^1\)。我们可以构造\((T^k)^{inv}=T^{k*inv}=T^1\),所以我们现当于要求\(k\)的逆元\(inv\)在\(mod\ len\)意义下。因此我们可以处理出每个置换中的环,然后计算所有的\(inv\),最后答案就是每个环位移每个环\(inv\)次。由于在一个置换上我们可以\(O(n)\)求出任意\(k\)次置换,所以本题总体复杂度是\(O(n)\)(具体理论参考《置换群快速幂运算 研究与探讨》(潘震皓))
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll input(){
ll x=0,f=0;char ch=getchar();
while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return f? -x:x;
}
const int N=1e5+7;
ll n,k;
int a[N],t[N],vis[N];
int Ans[N];
vector <int> cir;
void find(int x){
vis[x]=1;
cir.push_back(x);
if(!vis[t[x]]) find(t[x]);
}
void cal(){
int pos,len=cir.size();
for(int i=0;i<len;i++){
if(i*k%len==1){
pos=i;
break;
}
}
for(int i=0;i<len;i++){
Ans[cir[i]]=cir[(i+pos)%len];
}
}
int main(){
n=input(),k=input();
for(int i=1;i<=n;i++){
a[i]=t[i]=input();
}
for(int i=1;i<=n;i++){
cir.clear();
if(!vis[i]){
find(i);
cal();
}
}
for(int i=1;i<=n;i++) printf("%d%c",Ans[i],i==n? '\n':' ');
}
H
细节巨多的数据结构题。对于一个三角形来说已知一条边x,我们不妨设另外两条边为\(a,b(a>b)\),那么\(a,b,x\)一定满足不等式\(a-b<x<a+b\),有\(x<a+b\)我们易知\(a\)一定大于\(\left \lfloor \frac{x}{2} \right \rfloor\),这样我们就确定了上界,要如何确定下界呢?我们观察\(a-b<x\)要使答案满足条件,那么\(a-b\)一定是在满足\(x<a+b\)时越小越好,另外一个一定满足条件的情况是当\(a>\left \lfloor \frac{x}{2} \right \rfloor\)时离\(a\)最近的数\(b\)一定是一个可行解。那么问题便很简单了,我们用线段树维护相邻两数的差,每次询问现当于询问区间\((\left \lfloor \frac{x}{2} \right \rfloor,10^9]\)的最小值是否小于\(x\)。用set动态维护\(x\)的前驱和后继,注意对边界条件进行讨论即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll input(){
ll x=0,f=0;char ch=getchar();
while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return f? -x:x;
}
#define ls(x) t[x].l
#define rs(x) t[x].r
const ll inf=0x3f3f3f3f3f3f3f;
const int N=4e5+7;
struct node{
ll val;
int l,r;
}t[N*40];
int n,root,cnt;
multiset <ll> S;
map <ll,int> mp;
void update(int &rt,int l,int r,int pos,ll x){
if(!rt) rt=++cnt,t[rt].val=inf;
if(l==r){
t[rt].val=x;
return;
}
int mid=(l+r)>>1;
if(pos<=mid) update(ls(rt),l,mid,pos,x);
else update(rs(rt),mid+1,r,pos,x);
t[rt].val=min(t[ls(rt)].val,t[rs(rt)].val);
}
ll query(int rt,int l,int r,int ql,int qr){
if(!rt||r<l) return inf;
if(ql<=l&&r<=qr) return t[rt].val;
int mid=(l+r)>>1;
if(mid<ql) return query(rs(rt),mid+1,r,ql,qr);
if(qr<=mid) return query(ls(rt),l,mid,ql,qr);
return min(query(ls(rt),l,mid,ql,qr),query(rs(rt),mid+1,r,ql,qr));
}
void add(int x){
S.insert(x),mp[x]++;
auto pre=S.lower_bound(x),nxt=S.upper_bound(x);
if(mp[x]==1){
if(pre==S.begin()) update(root,1,(int)1e9,x,inf);
else{
--pre;
update(root,1,(int)1e9,x,x-*pre);
}
}else if(mp[x]==2) update(root,1,(int)1e9,x,0);
if(nxt!=S.end()){
if(mp[*nxt]==1){
update(root,1,(int)1e9,*nxt,*nxt-x);
}
}
}
void del(int x){
mp[x]--;
S.erase(S.find(x));
auto pre=S.lower_bound(x),nxt=S.upper_bound(x);
if(mp[x]==1){
if(pre!=S.begin()) --pre,update(root,1,(int)1e9,x,x-*pre);
else update(root,1,(int)1e9,x,inf);
}
if(mp[x]==0){
update(root,1,(int)1e9,x,inf);
if(nxt!=S.end()){
if(mp[*nxt]==1){
if(pre!=S.begin()) --pre,update(root,1,(int)1e9,*nxt,*nxt-*pre);
else update(root,1,(int)1e9,*nxt,inf);
}
}
}
}
ll get(int x){
auto it=S.lower_bound(x/2);
if(it==S.end()) return 1e9;
while(it!=S.end()){
if(it!=S.begin()){
int tmp=*it;
--it;
tmp+=*it;
if(tmp>x){
++it;
return *it;
}
++it;
}
++it;
}
return 1e9;
}
int main(){
t[0].val=inf;
int q=input();
for(int i=1;i<=q;i++){
int op=input(),x=input();
if(op==1){
add(x);
}else if(op==2){
del(x);
}else{
if(query(root,1,(int)1e9,get(x),(int)1e9)<x) printf("Yes\n");
else printf("No\n");
}
}
}