2020牛客暑期多校训练营(第二场)题解
A. All with Pairs(KMP+Hash+map)
思路:
我们很容易想到的一个方法就是,就算出每个字符串的每个后缀的哈希值将其存入一个$map$,然后计算每个前缀的哈希值,在$map$中查看有多少个后缀的哈希值与其相同,累加即为答案
但是这样做有时候我们会重复计数,比如在对$“aba”$ 与 $"caba"$比对时,后缀$"a"$,与后缀$“aba”$都会被统计到,但是我们只希望最长的那个被统计
现在我们考虑如何去除重复部分,我们可以考虑用$kmp$算法中的$next$数组来帮助我们去除重复
我们知道对于每个位置$i$的$nxt[i]$值,会有这样的性质,$S_{1...nxt[i]}=S_{i-nxt[i]+1...i}$
又由于,如果两个字符串的匹配长度为$i$时,会有$S_{1...nxt[i]}=T_{|t|-nxt[i]+1...|t|}$,所以$S_{i-nxt[i]+1...i}=T_{|t|-nxt[i]+1...|t|}$
这样我们就知道了对于长度为$i$的前缀,会在$nxt[i]$处发生一起重叠计算,在计算时我们在减去重叠部分即可
#include<iostream> #include<algorithm> #include<map> using namespace std; typedef unsigned long long ull; typedef long long ll; const int maxn=1e6+10; const ll mod=998244353; const int p = 233; map<ull,int> m; string s[maxn]; int nxt[maxn]; ll tmp[maxn]; void get_next(string w){ int len=w.size(),k=-1; nxt[0]=-1; for(int i=1;i<len;i++){ while(k>-1&&w[k+1]!=w[i]) k=nxt[k]; if(w[k+1]==w[i]) k++; nxt[i]=k; } } void get_hash(string s) { ull t=0,k=1; for(int i=s.length()-1;i>=0;i--){ t+=(s[i]-'a'+1)*k; k*=p; m[t]++; } } int main() { ll ans=0,n; cin>>n; for(int i=1;i<=n;i++){ cin>>s[i]; get_hash(s[i]); } for(int i=1;i<=n;i++){ ull t=0; get_next(s[i]); for(int j=0;j<s[i].length();j++){ t=t*p+(s[i][j]-'a'+1); tmp[j]=m[t]; } for(int j=0;j<s[i].length();j++) if(nxt[j]>=0) tmp[nxt[j]]-=tmp[j]; for(ll j=0;j<s[i].length();j++){ ans+=(tmp[j]%mod)*((j+1)*(j+1)%mod); ans%=mod; } } cout<<ans; return 0; }
B - Boundary(sort)
思路:
枚举两个点确定出圆心直接计数即可。
这里通过$sort$来计数,比直接$map$来要快一些
#include<bits/stdc++.h> #define MP make_pair #define fi first #define se second #define pb push_back #define sz(x) (int)(x).size() #define all(x) (x).begin(), (x).end() #define INF 0x3f3f3f3f using namespace std; typedef long long ll; typedef pair<int, int> pii; //head const int N = 2000 + 5; const double eps = 1e-10; typedef pair<double, double> pdd; int n; struct Point { int x, y; } a[N]; pdd solve(Point a, Point b, Point c) //三点共圆圆心公式 { double fm1=2 * (a.y - c.y) * (a.x - b.x) - 2 * (a.y - b.y) * (a.x - c.x); double fm2=2 * (a.y - b.y) * (a.x - c.x) - 2 * (a.y - c.y) * (a.x - b.x); if (fm1 == 0 || fm2 == 0) { return MP(1e18, 1e18); } double fz1=a.x * a.x - b.x * b.x + a.y * a.y - b.y * b.y; double fz2=a.x * a.x - c.x * c.x + a.y * a.y - c.y * c.y; double X = (fz1 * (a.y - c.y) - fz2 * (a.y - b.y)) / fm1; double Y = (fz1 * (a.x - c.x) - fz2 * (a.x - b.x)) / fm2; return MP(X, Y); } bool operator == (pdd A, pdd B) { return fabs(A.fi - B.fi) <= eps && fabs(A.se - B.se) <= eps; } void run() { cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i].x >> a[i].y; } vector<pdd> res; for (int i = 1; i <= n; i++) { for (int j = i + 1; j <= n; j++) { pdd now = solve({0, 0}, a[i], a[j]); if (now == MP(1e18, 1e18)) continue; res.push_back(now); } } if (sz(res) == 0) { cout << 1 << '\n'; return; } sort(all(res)); int ans = 1, t = 1; pdd now = res[0]; for (int i = 1; i < sz(res); i++) { if (res[i] == now) { ++t; } else { ans = max(ans, t); now = res[i]; t = 1; } } ans = max(ans, t); for (int i = 2; i <= n; i++) { if (i * (i - 1) == 2 * ans) { cout << i << '\n'; return; } } } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cout << fixed << setprecision(20); run(); return 0; }
C - Cover the Tree(DFS)
思路:
最少链的个数为$\left \lceil \frac{num}{2}\right \rceil$,$num$为叶子节点的个数
对于$n≤2$的情况,直接将两个节点相连
当$n≥3$时,链连接的方法为,我们先找出一个非叶子作为根,进行$dfs$,求出每个叶子节点的$dfs$序
之后,将$l_{1}$与$l_{\frac{num}{2}+1}$,$l_{1}$与$l_{\frac{num}{2}+2}$进行配对以此类推,如果$num$为奇数,则将最后一个点与根节点进行配对
#include<iostream> #include<algorithm> #include<vector> using namespace std; const int maxn=2e5+10; vector<int> a[maxn],ans; int d[maxn]; void dfs(int x,int fa) { int son=0; for(int i=0;i<a[x].size();i++){ int v=a[x][i]; if(v==fa) continue; son++; dfs(v,x); } if(son==0) ans.push_back(x); } int main() { int n,u,v; scanf("%d",&n); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); a[u].push_back(v); a[v].push_back(u); d[u]++,d[v]++; } int rt=1; for(int i=1;i<=n;i++){ if(d[i]>1){ rt=i; } } dfs(rt,0); int num=(ans.size()+1)/2; cout<<num<<endl; for(int i=0;i+num<ans.size();i++){ cout<<ans[i]<<" "<<ans[i+num]<<endl; } if(ans.size()%2) cout<<rt<<" "<<ans[ans.size()/2]<<endl; }
D - Duration(思维)
思路:
将两个时间分别化成秒,答案就为两个时间相减的绝对值
#include"bits/stdc++.h" using namespace std; int main() { int a,b,c,ans1=0,ans2=0; scanf("%d",&a); getchar(); scanf("%d",&b); getchar(); scanf("%d",&c); ans1=c+60*(b+60*a); scanf("%d",&a); getchar(); scanf("%d",&b); getchar(); scanf("%d",&c); ans2=c+60*(b+60*a); printf("%d\n",abs(ans1-ans2)); return 0; }
F. - Fake Maxpooling(单调队列)
思路:
先求出$lcm$矩阵,暴力求可能会超时,应该用记忆化方法来求
之后求每个子矩阵最大值用到二维单调队列去求,整个算是时间复杂度为$O(n*m)$
#include<iostream> #include<algorithm> #include<cstring> using namespace std; typedef long long ll; const int maxn=5001; int a[maxn][maxn],mx[maxn][maxn],q[maxn],q2[maxn]; int main() { int n,m,k; cin>>n>>m>>k; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(!mx[i][j]) for(int k=1;k*i<=n&&k*j<=m;k++){ mx[i*k][j*k]=k; a[i*k][j*k]=i*j*k; } } } memset(mx,0,sizeof(mx)); int h=1,t=0; for(int i=1;i<=n;i++){ h=1,t=0; for(int j=1;j<=m;j++){ while(h<=t&&q[h]<(j-k+1)) h++; while(h<=t&&a[i][q[t]]<=a[i][j]) t--; q[++t]=j; mx[i][j]=a[i][q[h]]; } } for(int i=k;i<=m;i++){ h=1,t=0; for(int j=1;j<=n;j++){ while(h<=t&&q2[h]<(j-k+1)) h++; while(h<=t&&mx[q2[t]][i]<=mx[j][i]) t--; q2[++t]=j; mx[j][i]=mx[q2[h]][i]; } } ll ans=0; for(int i=k;i<=n;i++) for(int j=k;j<=m;j++) ans+=mx[i][j]; cout<<ans; }
G - Greater and Greater(bitset)
思路:
对于每个$b_{i}$,我们建立一个$bitset$,如果$a_{j}≥b_{i}$,那么$b_{i}$对应的$bitset$的第$j$位就为$1$
如果暴力求出每个$bitset$肯定会超时,所以我们对$a$数组与$b$数组进行从大到小排序,就可以在$O(n+m)$的时间复杂度内求出每个$bitset$(具体实现看代码),并且由于经过排序,我们可以只使用一个$bitset$来存储信心
如果对于第$i$个$bitset$的位置$j$上为1,那么在$j-pos[i]+1$位置上的元素就有可能成为一个可行子数组的开头,我们对没个$biset$的所有可行开头求一个交集,交集中$1$的个数就为答案
#include<iostream> #include<algorithm> #include<bitset> using namespace std; const int maxn=150000+10; int a[maxn],b[maxn],p1[maxn],p2[maxn]; bitset<maxn> ans,now; int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=m;i++) scanf("%d",&b[i]); iota(p1+1,p1+n+1,1); iota(p2+1,p2+m+1,1); sort(p1+1,p1+n+1,[&](int i,int j){ return a[i]>a[j]; }); sort(p2+1,p2+m+1,[&](int i,int j){ return b[i]>b[j]; }); ans.set(); int pos=1; for(int i=1;i<=m;i++){ while(pos<=n&&a[p1[pos]]>=b[p2[i]]){ now.set(p1[pos]); pos++; } ans&=(now>>(p2[i]-1)); } cout<<ans.count(); }
H - Happy Triangle
思路:
我们先分析操作三,对于$x$我们可以将其分为两种情况讨论
如果$x$是三角形的最长边,我们我们肯定希望在小于$x$的数中找到最大的两个观察是否可行,这个操作可以用$map$与$set$完成
如果不是最长边,我们假设最长边为$b$,另外一条边为$a$,那么我们必须满足$x+a>b$这个不等式,移项一下$x>b-a$
要想使$b-a$尽可能的小,我们自然是希望$b$跟$a$在排序过集合中是相邻的两项
所以我们先将所有数读入,之后进行离散化建立线段树,线段树的维护的是集合中元素$i$与比左边元素的差值,如果集合中不存在比其小的值,则设置为$inf$
#include<iostream> #include<algorithm> #include<set> #include<map> #define inf 0x3f3f3f3f #define ls rt<<1 #define rs rt<<1|1 using namespace std; typedef long long ll; const int maxn=2e5+10; ll sum[maxn<<2]; //sum为线段树区间最小值数组 int op[maxn],x[maxn],y[maxn];//离线存储每次操作的数组,y数组为离散化后的数组 set<int> s; map<int,int> m,m1;//m1为存储离散化后的位置 void push_up(int rt) { sum[rt]=min(sum[ls],sum[rs]); } void build(int l,int r,int rt) { if(l==r){ sum[rt]=inf; return; } int mid=(l+r)>>1; build(l,mid,ls); build(mid+1,r,rs); push_up(rt); return; } void update(int l,int r,int rt,int pos,ll val) { if(l==r){ sum[rt]=val; return; } int mid=(l+r)>>1; if(pos<=mid) update(l,mid,ls,pos,val); else update(mid+1,r,rs,pos,val); push_up(rt); } ll query(int l,int r,int L,int R,int rt) { if(l>=L&&r<=R) return sum[rt]; int mid=(l+r)>>1; if(R<=mid) return query(l,mid,L,R,ls); if(L>mid) return query(mid+1,r,L,R,rs); return min(query(l,mid,L,R,ls),query(mid+1,r,L,R,rs)); } int main() { int n,tot,q; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%lld%lld",&op[i],&x[i]); y[i]=x[i]; } sort(y+1,y+1+n); tot=unique(y+1,y+1+n)-y-1; for(int i=1;i<=tot;i++) m1[y[i]]=i; build(1,tot,1); set<int> ::iterator it,it1,it2; for(int i=1;i<=n;i++){ if(op[i]==1){ m[x[i]]++; if(m[x[i]]==1){ s.insert(x[i]); it=it1=it2=s.find(x[i]); it2++; if(it!=s.begin()){//如果有比x小的数,我们就更新x在线段树中的值为两个数的差值 it1--; update(1,tot,1,m1[x[i]],x[i]-(*it1)); } if(it2!=s.end()&&m[*it2]==1){//如果有比x大的数,并且其个数只能为1,因为如果个数位置,其最小差值就为0 update(1,tot,1,m1[*it2],(*(it2)-x[i])); } } else update(1,tot,1,m1[x[i]],0);//有两个或以上一样的x,差值最小值就为0 } if(op[i]==3){ it1=it2=it=s.lower_bound(x[i]); if(m[x[i]]>=2){ printf("Yes\n"); continue; } if(m[x[i]]==1&&it!=s.begin()){ printf("Yes\n"); continue; } if(it!=s.begin()){ it1--; if(m[*it1]>=2){ if((*it1)*2>x[i]){ printf("Yes\n");continue; } } int tmp=*it1; if(it1!=s.begin()){ it1--; tmp+=*it1; if(tmp>x[i]){ printf("Yes\n");continue; } } } if(query(1,tot,m1[x[i]],tot,1)<x[i]) printf("Yes\n"); else printf("No\n"); } if(op[i]==2){ m[x[i]]--; if(m[x[i]]==1){//只剩下一个x it=s.find(x[i]); if(it!=s.begin()){ it--; update(1,tot,1,m1[x[i]],x[i]-(*it));//原先为0,更新为与前面数的差值 } else update(1,tot,1,m1[x[i]],inf);//前面没有数,由于不能删除线段树的点,所以将其值设为无穷 } if(m[x[i]]==0){ it=it1=it2=s.find(x[i]); it2++; it1--; if(it2!=s.end()&&m[*it2]==1){ if(it!=s.begin()){ update(1,tot,1,m1[*it2],(*it2)-(*it1)); } else update(1,tot,1,m1[*it2],inf); } update(1,tot,1,m1[x[i]],inf); s.erase(x[i]); } } } return 0; }
J - Just Shuffle(群论)
思路:
因为$k$为质数,所以在置换时循环大小不会变化,所以给定排列的循环大小就是置换的循环大小
对于每一个循环单独考虑,将$k mod len$当做一次置换(注意给出的是置换完的样子,而不是置换)
那么只要找到$x*k mod len =1$时的$x$,做$x$次置换,就可以得到答案
#include"bits/stdc++.h" #define ll long long using namespace std; int a[100005]; int save[100005]; int ans[100005]; struct _ { int ts; int pos; }; ll mod; int gcd(int a,int b) { if(a==0||b==0)return a+b; return gcd(b,a%b); } ll pows(ll a,ll b) { ll ans=1; for(;b;b>>=1,a=a*a%mod) { if(b%2)ans=ans*a%mod; } return ans%mod; } int eular(int n) { int ans=1,i; for(i=2;i*i<=n;i++) { if(n%i==0) { n/=i,ans*=i-1; while(n%i==0)n/=i,ans*=i; } } if(n>1) ans*=n-1; return ans; } int solve(int a,int b) { a%=b; if(gcd(a,b)>1)return -1; mod=(ll)b; return (int)pows((ll)a,(ll)eular(b)-1); } int main() { int n,k; cin>>n>>k; vector<struct _>v; for(int i=1;i<=n;i++) { scanf("%d",a+i); ans[i]=i; } for(int i=1;i<=n;i++)if(save[i]==0) { int t=0,p=i; while(1) { if(save[p]==1)break; save[p]=1; t++; p=a[p]; } struct _ test; test.ts=t; test.pos=i; if(t>1) v.push_back(test); } for(vector<struct _>::iterator it=v.begin();it!=v.end();it++) { int c=solve(k,it->ts); if(c==-1) { printf("-1\n"); v.clear(); return 0; } int t=c,p=(it->pos),p1,p2; p2=p; t--; while(t--) {//cout<<p<<endl; p2=a[p2]; } p1=p; t=(it->ts); while(t--) { ans[p1]=a[p2]; p1=a[p1]; p2=a[p2]; } } v.clear(); for(int i=1;i<=n;i++) { if(i==1)printf("%d",ans[1]); else printf(" %d",ans[i]); } putchar(10); return 0; }