• SDU暑期集训排位(2)题解


    A - Art

    签到题。暴力枚举累计答案即可。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 int a[5];
     5 int main() {
     6     for(int i=0;i<5;i++) scanf("%d",a+i);
     7     std::sort(a,a+5);int ans=0;
     8     for(int i=0;i<5;i++) for(int j=i+1;j<5;j++) for(int k=j+1;k<5;k++) if(a[i]+a[j]>a[k]) ans++;
     9     printf("%d
    ",ans);
    10     return 0;
    11 }
    View Code

    B - Biology

    模拟题。总共有$100$种牌,去掉手中的两张,在剩下的$98$张中枚举所有的可能,然后按照题意确定手牌类型累加答案。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<vector>
     5 #include<algorithm>
     6 using namespace std;
     7 typedef pair<int,int>P;
     8 P a[6];
     9 int rk[30], su[10];
    10 int check(){
    11     for(int i=0;i<25;i++) rk[i]=0;
    12     for(int i=0;i<4;i++) su[i]=0;
    13     bool st=true;
    14     for(int i=1;i<=5;i++){
    15         rk[a[i].first]++; su[a[i].second]++; if(i==1) continue;
    16         if(a[i].first-a[i-1].first!=1) st=false;
    17     }
    18     if(st&&su[a[1].second]==5) return 1;
    19     for(int i=1;i<=5;i++) if(rk[a[i].first]>=4) return 2;
    20     for(int i=1;i<=5;i++){
    21         for(int j=1;j<=5;j++) if(a[i].first!=a[j].first&&rk[a[i].first]==3&&rk[a[j].first]==2) return 3;
    22     }
    23     if(su[a[1].second]==5) return 4;
    24     if(st) return 5;
    25     for(int i=1;i<=5;i++) if(rk[a[i].first]>=3) return 6;
    26     for(int i=1;i<=5;i++) for(int j=1;j<=5;j++){
    27         if(a[i].first!=a[j].first&&rk[a[i].first]>=2&&rk[a[j].first]>=2) return 7;
    28     }
    29     for(int i=1;i<=5;i++) if(rk[a[i].first]>=2) return 8;
    30     return 9;
    31 }
    32 int n, m;
    33 int a1, a2, b1, b2;
    34 P get(int x){
    35     return P(x/m,x%m);
    36 }
    37 int ans[10];
    38 int main(){
    39     scanf("%d%d",&n,&m);
    40     scanf("%d%d%d%d",&a1,&a2,&b1,&b2);
    41     P f=P(a1,a2); P g=P(b1,b2); int v1=a1*m+a2; int v2=b1*m+b2;
    42     for(int i=0;i<n*m;i++){
    43         if(i==v1||i==v2) continue;
    44         for(int j=i+1;j<n*m;j++){
    45             if(j==v1||j==v2) continue;
    46             for(int k=j+1;k<n*m;k++){
    47                 if(k==v1||k==v2) continue;
    48                 a[1]=get(i); a[2]=get(j); a[3]=get(k);  a[4]=f; a[5]=g;
    49                 sort(a+1,a+5+1);
    50                 ans[check()]++;
    51             }
    52         }
    53     }
    54     for(int i=1;i<=9;i++) printf("%d%c",ans[i],(i==9?'
    ':' '));
    55     return 0;
    56 }
    View Code

    C - Computer Science

    单调队列。首先,很显然的一个性质是,如果我们将$a$排序,那么每个区间包含的肯定是$a$的一个子区间,且从贪心的角度考虑,我们肯定希望每个区间恰好包含$K$个点。因此,我们可以对每个$a_i$求出最短的区间的长度,然后在取$max$即可。为了更方便的求符合要求的包含$a_i$的最短区间的长度,我们可以定一个$b$,其中$b_i=a_i-a_{i-k+1}$,那么显然,包含的$a_i$的最短区间的长度为$min_{j=i}^{i+k-1}b_j$,这个显然可以用线段树或者$ST$表来求,但是还有更加简单的办法,就是单调队列,这里不赘述。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 typedef long long ll;
     5 const int N=2e5+5;
     6 ll a[N],b[N],f[N];
     7 int n,k;
     8 int q[N],l,r;
     9 int main() {
    10     scanf("%d%d",&n,&k);
    11     for(int i=1;i<=n;i++) scanf("%lld",a+i);
    12     std::sort(a+1,a+1+n);
    13     for(int i=k;i<=n;i++) b[i]=a[i]-a[i-k+1];
    14     l=r=0;
    15     for(int i=1;i<=n;i++) {
    16         if(i+k-1<=n) while(r>l&&b[i+k-1]<=b[q[r-1]]) --r;
    17         while(l<r&&q[l]<i) l++;
    18         q[r++]=i+k-1;
    19         f[i]=b[q[l]];
    20     }
    21     ll ans=0;
    22     for(int i=1;i<=n;i++) ans=std::max(ans,f[i]);
    23     printf("%lld
    ",ans);
    24     return 0;
    25 }
    View Code

    G - New Keyboard

    $dp$。对于这个题来说,比较容易想到的状态是用$dp[i][j]$表示已经输入了$i$个字符,最后一次输字符是在第$j$个$layout$下,这显然是可以的,但状态转移会比较麻烦,我们需要枚举下一个$layout$用哪个,同时,这样的时间复杂度也过高了。仔细想想可以发现,以上状态无法解决的问题无非是移动的代价,我们只能枚举距离,而无法直接移动到下一个$layout$,因此我们可以给状态加一维,用$dp[i][j][k]$表示已经输入了$i$个字符,当前是在第$j$个$layout$,并用$k=0$表示当前$layout$没有输入字符,$k=1$反之。状态转移的话,我们可以枚举$i$,然后显然,我们应该是先移动,再输入字符,对于移动来说,我们有$dp[i][j][0]=min(dp[i][(j-1+n)\%n+1][0]+b,dp[i][(j-1+n)\%n+1][1]+a)$,如果你把它的状态转移图画出来,它会成一个简单环,因此,我们可以暴力迭代,在$O(n)$的时间复杂度内就可以完成移动对应的状态转移,而输入字符就很简单了,这里不多说了,详细的细节请看代码。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 typedef long long ll;
     5 const int N=2005;
     6 ll dp[N][N][2];
     7 int f[N];
     8 char t[N];
     9 const ll INF=1e18;
    10 int main() {
    11     int n,a,b,c,m;
    12     scanf("%d%d%d%d",&n,&a,&b,&c);
    13     for(int i=0;i<n;i++) {
    14         scanf("%s",t);
    15         f[i]=0;
    16         for(int j=0;t[j];j++) f[i]|=1<<(t[j]-'0');
    17     }
    18     scanf("%s",t+1);
    19     m=strlen(t+1);
    20     for(int i=0;i<=m;i++) for(int j=0;j<n;j++) dp[i][j][0]=dp[i][j][1]=INF;
    21     dp[0][0][1]=0;
    22     for(int i=0;i<m;i++) {
    23         while(1) {
    24             bool f=false;
    25             for(int j=0;j<n;j++) {
    26                 ll t=std::min(dp[i][j][0]+b,dp[i][j][1]+a);
    27                 if(t<dp[i][(j+1)%n][0]) {
    28                     f=true;
    29                     dp[i][(j+1)%n][0]=t;
    30                 }
    31             }
    32             if(!f) break;
    33         }
    34         int v=t[i+1]-'0';
    35         for(int j=0;j<n;j++) if(f[j]>>v&1) {
    36             dp[i+1][j][1]=std::min(dp[i+1][j][1],std::min(dp[i][j][0],dp[i][j][1])+c);
    37         }
    38     }
    39     ll ans=INF;
    40     for(int i=0;i<n;i++) ans=std::min(ans,dp[m][i][1]);
    41     if(ans>=INF) ans=-1;
    42     printf("%lld
    ",ans);
    43     return 0;
    44 }
    View Code

    H - Folding the Figure

    $暴力搜索$。首先,很容易发现$k<=n+n$,因为图形折叠以后至少会保留一半的面积。然后,对称轴只有四个,且四个都有解!因此,我们随便找其中一个对称轴,然后暴力的搜索一个大小为$k-n$的连通块,并把坐标按照对称轴对称一下,就是答案。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<vector>
     5 #include<set>
     6 const int N=1e5+5;
     7 typedef std::pair<int,int> P;
     8 P a[N];
     9 int n,k,T;
    10 std::set<P> s,t;
    11 const int fx[4]={0,0,1,-1};
    12 const int fy[4]={1,-1,0,0};
    13 void dfs(int x,int y,int up) {
    14     if(t.size()>=up) return;
    15     t.insert(P(x,y));
    16     for(int i=0;i<4;i++) {
    17         P c(x+fx[i],y+fy[i]);
    18         if(s.count(c)&&!t.count(c)) dfs(c.first,c.second,up);
    19     }
    20 }
    21 int main() {
    22     scanf("%d",&T);
    23     while(T--) {
    24         scanf("%d%d",&n,&k);
    25         std::vector<P> ans;s.clear();t.clear();
    26         for(int i=1;i<=n;i++) {
    27             scanf("%d%d",&a[i].first,&a[i].second);
    28             ans.push_back(a[i]);
    29             s.insert(a[i]);
    30         }
    31         std::sort(a+1,a+1+n);
    32         printf("L %d
    ",a[1].first);
    33         dfs(a[1].first,a[1].second,k-n);
    34         for(P c:t) ans.push_back(P(a[1].first+a[1].first-c.first-1,c.second));
    35         std::sort(ans.begin(),ans.end());
    36         for(P c:ans) printf("%d %d
    ",c.first,c.second);
    37     }
    38     return 0;
    39 }
    View Code

    I-Acute Triangles

    显然钝角三角形的数目就是钝角的数目(直角也一样),然后每个锐角三角形会贡献三个锐角,每个钝角三角形贡献一个钝角和两个锐角(直角一样)。

    就有各种方法可以求锐角三角形的数目。

    是18北京J题简化版的简化版的简化版

    极角排序扫一圈就行了。

     1 #include <bits/stdc++.h>
     2 using namespace std;
     3 typedef long long ll;
     4 struct point{
     5     ll x,y;
     6     point operator + (const point &k1) const{return (point){k1.x+x,k1.y+y};}
     7     point operator - (const point &k1) const{return (point){x-k1.x,y-k1.y};}
     8     inline int getP() const{return y>0||(y==0&&x<0);}
     9 };
    10 ll cross(point k1,point k2){return k1.x*k2.y-k1.y*k2.x;}
    11 int compareangle (point k1,point k2){//极角排序+
    12     return k1.getP()<k2.getP()||(k1.getP()==k2.getP()&&cross(k1,k2)>0);
    13 }
    14 int t,n;point p[2019];
    15 vector<point> v;
    16 ll dj,qb,rj;
    17 ll slove(int id){
    18     v.clear();
    19     for(int i=1;i<=n;i++)if(i!=id)v.push_back(p[i]-p[id]);
    20     sort(v.begin(),v.end(),compareangle);
    21     int m = v.size();
    22     for(int i=0;i<m;i++)v.push_back(v[i]);
    23     int q=0,s=0,t=0;//0,90,180
    24     for(int i=0;i<m;i++){
    25         point _90 = {-v[i].y,v[i].x};
    26         point _180 = {-v[i].x,-v[i].y};;
    27         q=max(i,q);
    28         while (q<i+m-1&&cross(v[i],v[q+1])==0)q++;
    29         while (s<i+m-1&&(s<q||(cross(v[s+1],_90)>0&&cross(v[s+1],v[i])<=0)))s++;
    30         while (t<i+m-1&&(t<s||(cross(v[t+1],_180)>0&&cross(v[t+1],_90)<=0)))t++;//取不到180
    31         dj+=t-s;
    32         qb+=t-q;
    33         rj+=s-q;
    34     }
    35 }
    36 int main(){
    37     scanf("%d",&t);
    38     while (t--){
    39         qb=dj=rj=0;
    40         scanf("%d",&n);
    41         for(int i=1;i<=n;i++){
    42             scanf("%lld%lld",&p[i].x,&p[i].y);
    43         }
    44         for(int i=1;i<=n;i++){
    45             slove(i);
    46 //            cout<<dj<<' '<<qb<<' '<<rj<<endl;
    47         }
    48 //        printf("%lld
    ",dj);
    49 //        printf("%lld
    ",qb);
    50 //        printf("%lld
    ",rj);
    51         ll ans = (qb-dj*3)/3;
    52         printf("%lld
    ",ans);
    53     }
    54 }
    55 /**
    56 1
    57  5
    58 1 1
    59 2 2
    60 3 3
    61 4 1
    62 6 4
    63  */
    View Code

    J - Joining Arrays

    贪心、$dp$。一般这类找字典序最小的题,都是从前往后贪心,枚举每一位,判断该位填某个数字之后是否有可行解,本题同理。现在的问题就是如何判断存在可行解?我们可以先把问题反过来思考,给你一个序列,如何判断它是$A$和$B$的一个$join$(不考虑字典序)?那么我们可能很容易想到一个三维$dp$,用$dp[i][j][k]$表示$A$匹配的最后一个位置为$j$,$B$匹配的最后一个位置为$k$,已经匹配了$i$个位置是否可行,然后这个东西其实可以变两维,用$dp[i][j]$表示$A$匹配的最后一个位置是$j$,已经匹配了$i$个位置时,$B$匹配到的最小位置,转移的话,我们可以对$A$和$B$都预处理一个$nxt$数组,维护第$i$个位置后的最近的数字$j$的位置,然后分别尝试用$A$和$B$去匹配下一个数字即可。现在我们考虑动态的维护这个$dp$,初始化$dp[0][0]=0$,假如我们现在已经填了$i-1$个数字了,接下来要填第$i$个,我们可以暴力枚举要填的数字,然后就可以进行$dp$,完成$dp$后,我们只要$check$一下是否存在这样的$j$,使得$n-j+m-dp[i][j] geq k-i$,如果存在,则该数字可以填,否则不可以。这样的复杂度是$O(nkC)$,其中$C$是数字种类数,这个复杂度显然无法接受,但是仔细想想可以发现,我们没有必要枚举每一位放什么,我们完全可以二分,即,我们可以二分一个$x$,然后$check$一下$[1,x]$中是否含有一个数字存在可行解,原来是$check$一个,现在是$check$区间,这个怎么做呢?其实很简单,我们只要把$nxt$数组对数字累计前缀最小即可,即用$nxt[i][j]$表示第$i$个位置后,最近的一个属于$[1,j]$的数字的位置,其他的跟原来一样,这样时间复杂度就变为了$O(nklogC)$。但是我们并没有做完,题目中还有一个限制条件,就是$A$和$B$都必须对答案有贡献,即,最终答案不能只存在于$A$或$B$中,其实这个很容易搞定,我们先按照之前的做法找到一个序列,然后在$check$一下这个序列是否可以只存在于$A$或者$B$中,如果可以只存在于$A$,那么我们就把序列的最后一个数字换成$B$序列的最小值,另一种情况反之。

     1 #include<iostream>
     2 #include<cstdio>
     3 const int N=3001;
     4 typedef std::pair<int,int> P;
     5 int n,m,k,nxt[2][N][N],pos[N],a[2][N],ans[N<<1],pre[2][N][N];
     6 int dp[2][N];
     7 void init(int n,int nxt[N][N],int pre[N][N],int a[N]) {
     8     for(int i=1;i<=n;i++) scanf("%d",a+i);
     9     for(int i=1;i<N;i++) pos[i]=n+1;
    10     for(int i=n;~i;i--) {
    11         for(int j=1;j<N;j++) nxt[i][j]=pos[j];
    12         pos[a[i]]=i;
    13         pre[i][1]=nxt[i][1];
    14         for(int j=2;j<N;j++) pre[i][j]=std::min(nxt[i][j],pre[i][j-1]);
    15     }
    16 }
    17 int main() {
    18     scanf("%d",&n);
    19     init(n,nxt[0],pre[0],a[0]);
    20     scanf("%d",&m);
    21     init(m,nxt[1],pre[1],a[1]);
    22     scanf("%d",&k);
    23     for(int i=0;i<=n;i++) dp[0][i]=m+1;
    24     dp[0][0]=0;
    25     int *f=dp[0],*g=dp[1];
    26     for(int i=1,l,r,mid;i<=k;i++) {
    27         l=1,r=N-1;
    28         while(l<r) {
    29             bool ok=false;
    30             mid=(l+r)>>1;
    31             for(int j=0;j<=n;j++) g[j]=m+1;
    32             for(int j=0;j<=n;j++) if(f[j]<=m) {
    33                 int p=pre[0][j][mid];
    34                 if(p<=n) g[p]=std::min(g[p],f[j]);
    35                 g[j]=std::min(g[j],pre[1][f[j]][mid]);
    36             }
    37             for(int j=0;j<=n&&!ok;j++) if(g[j]<=m) {
    38                 if(n-j+m-g[j]>=k-i) ok=true;
    39             }
    40             if(ok) r=mid;
    41             else l=mid+1;
    42         }
    43         ans[i]=r;
    44         for(int j=0;j<=n;j++) g[j]=m+1;
    45         for(int j=0;j<=n;j++) if(f[j]<=m) {
    46             int p=nxt[0][j][r];
    47             if(p<=n) g[p]=std::min(g[p],f[j]);
    48             g[j]=std::min(g[j],nxt[1][f[j]][r]);
    49         }
    50         std::swap(f,g);
    51     }
    52     int p=1;
    53     for(int i=1;i<=n&&p<=k;i++) {
    54         if(a[0][i]==ans[p]) p++;
    55     }
    56     if(p>k) {
    57         ans[k]=a[1][1];
    58         for(int i=2;i<=m;i++) ans[k]=std::min(ans[k],a[1][i]);
    59     }
    60     else {
    61         p=1;
    62         for(int i=1;i<=m&&p<=k;i++) {
    63             if(a[1][i]==ans[p]) p++;
    64         }
    65         if(p>k) {
    66             ans[k]=a[0][1];
    67             for(int i=2;i<=n;i++) ans[k]=std::min(ans[k],a[0][i]);
    68         }
    69     }
    70     for(int i=1;i<=k;i++) printf("%d%c",ans[i]," 
    "[i==k]);
    71     return 0;
    72 }
    View Code

     第二种解法:首先呢我们枚举在第一个序列中占了几个,然后这样可以用单调队列处理第一个序列种取出x个的最小字典序的子序列,第二个同理。接下来就是

    用SA来将他们拼起来,当然这个地方有一个坑,就是对于如果是前缀的话,我们应该选择那个长的,这样能得到更多的机会。所以第一个和第二串中间还有第二串末尾都应该加上mx+1。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<vector>
    #include<algorithm>
    #include<map>
    using namespace std;
    const int maxn=6e3+5;
    const int maxm=3e3+5;
    int n, m, a[maxn], b[maxn], k;
    int mx=3000, ans[maxn], res[maxn];
    int s[maxn];
    struct SuffixArray{
        int sa[maxn],rank[maxn],height[maxn],cnt[maxn],a1[maxn],a2[maxn],n,m,*x,*y;
        void sort() {
            for(int i=0;i<m;i++) cnt[i]=0;
            for(int i=0;i<n;i++) cnt[x[i]]++;
            for(int i=1;i<m;i++) cnt[i]+=cnt[i-1];
            for(int i=n-1;i>=0;i--) sa[--cnt[x[y[i]]]]=y[i];
        }
        void build(int *s,int tn,int c_size) {
            n=tn;m=c_size;
            x=a1;y=a2;
            for(int i=0;i<n;i++) x[i]=s[i],y[i]=i;x[n]=y[n]=-1;
            sort();
            for(int k=1;k<=n;k<<=1) {
                int p=0;
                for(int i=n-k;i<n;i++) y[p++]=i;
                for(int i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
                sort();
                p=0;std::swap(x,y);
                x[sa[0]]=0;
                for(int i=1;i<n;i++) {
                    if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k]) p++;
                    x[sa[i]]=p;
                }
                if(p+1>=n) break;
                m=p+1;
            }
            for(int i=0;i<n;i++) rank[sa[i]]=i;
            height[0]=0;int k=0;
            for(int i=0;i<n;i++) {
                if(k) k--;
                if(rank[i]==0) continue;
                int j=sa[rank[i]-1];
                while(i+k<n&&j+k<n&&s[i+k]==s[j+k]) k++;
                height[rank[i]]=k;
            }
        }
    }SA;
    bool cmp(){
        for(int i=0;i<k;i++){
            if(res[i]<ans[i]) return false;
            if(res[i]>ans[i]) return true;
        }
        return false;
    }
    int t[maxm];
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        scanf("%d",&m);
        for(int i=1;i<=m;i++) scanf("%d",&b[i]);
        scanf("%d",&k);
        for(int i=0;i<k;i++) res[i]=mx+1;
        for(int w=1;w<=min(n,k-1);w++){
            if(k-w>m) continue;
            int head=1, tail=1; int p=0;
            for(int i=1;i<=n;i++){
                while(tail>head&&a[i]<a[t[tail-1]]) tail--;
                t[tail++]=i;
                if(i>=n-w+1){
                    s[p++]=a[t[head]]; head++;
                }
            }
            s[p++]=mx+1;
            head=1, tail=1;
            for(int i=1;i<=m;i++){
                while(tail>head&&b[i]<b[t[tail-1]]) tail--;
                t[tail++]=i;
                if(i>=m-(k-w)+1){
                    s[p++]=b[t[head]]; head++;
                }
            }
            s[p++]=mx+1;
            SA.build(s,p,mx+2); int i=0, j=w+1; p=0;
            while(i<w&&j<=k){
                if(SA.rank[i]<SA.rank[j]) ans[p++]=s[i],i++;
                else ans[p++]=s[j],j++;
            }
            while(i<w) ans[p++]=s[i], i++;
            while(j<=k) ans[p++]=s[j], j++;
            if(cmp()){
                for(int i=0;i<k;i++) res[i]=ans[i];
    
            }
        }
        for(int i=0;i<k;i++) printf("%d ",res[i]);
        printf("
    ");
        return 0;
    }
    View Code
  • 相关阅读:
    XGBoost算法--学习笔记
    机器学习--学习书籍
    一天搞懂深度学习-深度学习新浪潮
    如何在 Office 365 环境中设置联机 Exchange 邮箱大小和限制
    玩转Office 365中的Exchange Online服务 之十一 怎样在Exchange Online中配置邮件传递限制《转》
    玩转Office 365中的Exchange Online服务 之六 了解Exchange Online对于邮箱使用的限制《转》
    Hyper-V Ubuntu修改分辨率
    k8s 集群基本概念<转>
    Azure 中 Linux VM 的 SSH 公钥和私钥对
    docker学习笔记(k8s) 《转》
  • 原文地址:https://www.cnblogs.com/Onlymyheart/p/11260540.html
Copyright © 2020-2023  润新知