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;
}

 

posted @ 2020-07-16 11:12  overrate_wsj  阅读(226)  评论(0编辑  收藏  举报