• 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 << '
    ';
            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 << '
    ';
                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} ight ceil$,$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
    ",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
    ");
                    continue;
                }
                if(m[x[i]]==1&&it!=s.begin()){
                    printf("Yes
    ");
                    continue;
                }
                if(it!=s.begin()){
                    it1--;
                    if(m[*it1]>=2){
                        if((*it1)*2>x[i]){
                            printf("Yes
    ");continue;
                        }
                    }
                    int tmp=*it1;
                    if(it1!=s.begin()){
                        it1--;
                        tmp+=*it1;
                        if(tmp>x[i]){
                            printf("Yes
    ");continue;
                        }
                    }
                }
                if(query(1,tot,m1[x[i]],tot,1)<x[i]) printf("Yes
    ");
                else printf("No
    ");
            }
            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
    ");
                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;
    }
  • 相关阅读:
    May 1 2017 Week 18 Monday
    April 30 2017 Week 18 Sunday
    April 29 2017 Week 17 Saturday
    April 28 2017 Week 17 Friday
    April 27 2017 Week 17 Thursday
    April 26 2017 Week 17 Wednesday
    【2017-07-04】Qt信号与槽深入理解之一:信号与槽的连接方式
    April 25 2017 Week 17 Tuesday
    April 24 2017 Week 17 Monday
    为什么丑陋的UI界面却能创造良好的用户体验?
  • 原文地址:https://www.cnblogs.com/overrate-wsj/p/13321588.html
Copyright © 2020-2023  润新知