zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    Prototype源码浅析——String部分(四)之补充
    改造alert的引发的争论【基本类型与引用类型】
    Eclipse rcp 窗口激活
    Eclipse statusLine 加入进度信息
    线的匹配
    python 文本搜索
    Eclipse rcp 编辑器行号显示
    CDT重建索引
    Eclipse rcp 数据存储
    CTabFolder 最大最小化
  • 原文地址:https://www.cnblogs.com/overrate-wsj/p/13321588.html
Copyright © 2011-2022 走看看