zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    解释机器学习模型的一些方法(一)——数据可视化
    机器学习模型解释工具-Lime
    Hive SQL 语法学习与实践
    LeetCode 198. 打家劫舍(House Robber)LeetCode 213. 打家劫舍 II(House Robber II)
    LeetCode 148. 排序链表(Sort List)
    LeetCode 18. 四数之和(4Sum)
    LeetCode 12. 整数转罗马数字(Integer to Roman)
    LeetCode 31. 下一个排列(Next Permutation)
    LeetCode 168. Excel表列名称(Excel Sheet Column Title)
    论FPGA建模,与面向对象编程的相似性
  • 原文地址:https://www.cnblogs.com/Onlymyheart/p/11260540.html
Copyright © 2011-2022 走看看