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 }
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 }
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 }
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 }
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 }
显然钝角三角形的数目就是钝角的数目(直角也一样),然后每个锐角三角形会贡献三个锐角,每个钝角三角形贡献一个钝角和两个锐角(直角一样)。
就有各种方法可以求锐角三角形的数目。
是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 */
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 }
第二种解法:首先呢我们枚举在第一个序列中占了几个,然后这样可以用单调队列处理第一个序列种取出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; }