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\n",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?'\n':' '));
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\n",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\n",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\n",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\n",c.first,c.second);
37     }
38     return 0;
39 }
View Code

I-Acute Triangles

扫描二维码关注公众号,回复: 6888163 查看本文章

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

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

是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\n",dj);
49 //        printf("%lld\n",qb);
50 //        printf("%lld\n",rj);
51         ll ans = (qb-dj*3)/3;
52         printf("%lld\n",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]," \n"[i==k]);
71     return 0;
72 }
View Code

猜你喜欢

转载自www.cnblogs.com/Onlymyheart/p/11260540.html