Codeforces Round #541 (Div. 2) 题解
Codeforces Round #541 (Div. 2)
题目链接:https://codeforces.com/contest/1131
A. Sea Battle
题意:
给出两个矩形的宽和高,满足第一个矩形的左上顶点为(0,0),右下顶点为(w1,-h1);第二个矩形的坐下顶点为(0,0),右上顶点为(w2,h2)。然后求围绕这两个矩形一圈的格子个数为多少。
题解:
利用二维前缀和的思路推导一下公式就行了,公式也有很多吧==我当时就是大概模拟了一下。。
代码如下:
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll w1,h1,w2,h2; int main(){ ios::sync_with_stdio(false);cin.tie(0); cin>>w1>>h1>>w2>>h2; ll ans = 0; ans+=(h1+h2+2)*(max(w1,w2)+2); ll now=min(w1,w2); ll tmp=max(w1,w2)-now; if(w1<w2) ans-=h1*tmp; else ans-=h2*tmp; ans-=(w1*h1+w2*h2); cout<<ans; return 0; }
B. Draw!
题意:
给出n次两个人的比分,每次给出xi,yi代表两个人的比分,然后问这两个人最多平局的多少次。
题解:
假设这两个人在比分为p的时候平局,对于xi,yi,xi+1,yi+1来说,肯定满足max(xi,yi)<=p<=min(xi+1,yi+1)的,然后就借用这个式子来算。注意一下xi==yi这种特殊情况,可能会被计算多次,处理一下就好了。
原谅我代码对不上上面的题解。。懒得改了,我感觉我的做题思路还没有抽象化,上面一题也是,基本都是采用模拟的方法来做的,没有很好地进行数学建模。
代码如下:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 10005; int n; int a[N],x[N],y[N]; int main(){ ios::sync_with_stdio(false);cin.tie(0); cin>>n; ll ans = 1; for(int i=1;i<=n;i++) cin>>x[i]>>y[i]; ans+=min(x[1],y[1]); for(int i=2;i<=n;i++){ if(x[i]>y[i]){ int dx=x[i]-x[i-1],dy=y[i]-y[i-1]; if(y[i]<x[i-1]) continue ; if(y[i-1]<x[i-1]) ans+=(y[i]-x[i-1]+1); else if(y[i-1]>x[i-1]) ans+=dy+1; else ans+=min(dx,dy); }else{ int dx=x[i]-x[i-1],dy=y[i]-y[i-1]; if(x[i]<y[i-1]) continue ; if(x[i-1]<y[i-1]) ans+=(x[i]-y[i-1]+1); else if(x[i-1]>y[i-1]) ans+=dx+1; else ans+=min(dx,dy); } } cout<<ans; }
C. Birthday
题意:
n个人,每个人都有对应的身高ai,然后这n个人站成一个圈,问怎样的站法,能够使相邻两个人的身高差最小,最后任意输出一种方法即可。
题解:
考虑贪心的做法(为啥这题的数据为100啊...),先对所有的数排个序,然后从前往后依次放奇数位置的人,从后往前依次放偶数位置的人。
题解的证明用到了哈密顿回路,我们这种做法得到的答案为max(ai+2-ai),然后只需要证明不可能存在max(ai+1-ai)这样的答案就行了,证明这个在直观上面还是比较好理解的,很容易得证。
代码如下:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 105; int a[N],ans[N]; int n; int main(){ ios::sync_with_stdio(false);cin.tie(0); cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; sort(a+1,a+n+1); int fir=1,last=n; for(int i=1;i<=n;i++){ if(i&1){ ans[fir++]=a[i]; }else ans[last--]=a[i]; } for(int i=1;i<=n;i++) cout<<ans[i]<<" "; return 0; }
D. Gourmet choice
题意:
给出一个n*m的符号矩阵,分别对应第n个数和第m个数的大小关系,然后让你根据这个矩阵构造这n+m个数,如果得不到就输出"No"。
题解:
其实根据大小关系很容易想到拓扑排序,小的向大的连边,然后如果无环就存在一种情况。
但是这里有等于符号,在本题中,等于的那些数本质上是相等的,也就是说拥有相同的拓扑序,那我们用并查集缩点处理即可。
然后跑拓扑排序,途中标记答案,最后根据输出就行了。
注意一下这种情况:就是如果一些点都属于一个集合中,但是有些点的关系存在不等关系,这就矛盾了,要判断一下。
代码如下:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1005; int n,m,cnt,tot; char mp[N][N]; int f[N<<1],num[N<<1],head[N<<1],in[N<<1],ans[N<<1]; struct Edge{ int u,v,next; }e[N*N<<1]; int find(int x){ return f[x]==x?f[x]:f[x]=find(f[x]); } void Union(int x,int y){ int fx=find(x),fy=find(y); if(fx==fy) return ; f[fx]=fy; } void adde(int u,int v){ e[tot].v=v;e[tot].next=head[u];head[u]=tot++; } int topsort(){ queue <int> q; int tot=0; for(int i=1;i<=cnt;i++) if(!in[i]) q.push(i),tot++,ans[i]=1; while(!q.empty()){ int u=q.front();q.pop(); for(int i=head[u];i!=-1;i=e[i].next){ int v=e[i].v; if(--in[v]==0){ ans[v]=ans[u]+1; q.push(v); tot++; } } } return tot==cnt; } int main(){ scanf("%d%d",&n,&m); int flag=0; for(int i=1;i<=n+m;i++) f[i]=i; for(int i=1;i<=n;i++){ scanf("%s",mp[i]+1); for(int j=1;j<=m;j++){ if(mp[i][j]=='=') Union(i,j+n); else flag=1; } } for(int i=1;i<=n+m;i++){ int f=find(i); if(!num[f]) num[f]=++cnt; } if(cnt==1 && flag){ cout<<"No"; return 0; } memset(head,-1,sizeof(head)); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ int u=num[find(i)],v=num[find(j+n)]; if(u==v&&mp[i][j]!='='){ cout<<"No"; return 0; } if(mp[i][j]=='<') adde(u,v),in[v]++; else if(mp[i][j]=='>') adde(v,u),in[u]++; } } int f=topsort(); if(!f) cout<<"No"; else{ puts("Yes"); for(int i=1;i<=n+m;i++){ int fx=find(i); cout<<ans[num[fx]]<<" "; if(i==n) cout<<'\n'; } } return 0; }
F. Asya And Kittens
题意:
有n个数乱序排列,每个数都互不相等,并且它们中间有n-1个隔板将它们分离。现在每次会抽出两个数之中的隔板,进行n-1次操作,最终n个数之中没有隔板,最后输出这n个数排列的一种情况。注意题目中给出的两个数,不一定是相邻的,但它们一定在相邻的两个块中。
题解:
对于两个相邻的块,我们直接合并就行了,往左或者右合并都行,因为反正它们都会成为一个块。这里用并查集的话就要启发式合并,减少空间的消耗,具体点就是将size小的集合往size大的集合合并,这样可以节约总空间,并且可以证明空间复杂度不超过O(nlogn);如果不这样合并,空间复杂度上界可能为O(n^2)。
当然这里有个黑科技rope,用这个来储存十分节约空间,对于这个题怎么合并都行,并且单次插入、删除等操作都是O(logn)的复杂度,常数稍微大了点,但是已经十分优秀了。这个底层是用平衡树来搞的,似乎可以来代替块状链表= =反正就是个类似于链表的东西。
代码如下:
#include <bits/stdc++.h> #include <ext/rope> using namespace std; using namespace __gnu_cxx; typedef long long ll; const int N = 150005; int n; int f[N]; rope <int> a[N]; int find(int x){ return f[x]==x ? f[x] : f[x]=find(f[x]); } int main(){ ios::sync_with_stdio(false);cin.tie(0); cin>>n; for(int i=1;i<=n;i++) f[i]=i,a[i]=i; for(int i=1;i<n;i++){ int x,y; cin>>x>>y; int fx=find(x),fy=find(y); if(fx==fy) continue ; f[fx]=fy; a[fy]+=a[fx]; } for(int i=1;i<=n;i++){ int f=find(i); if(f==i){ for(int j=0;j<a[f].size();j++){ cout<<a[f][j]<<" "; } break ; } } return 0; }
最后再口胡一下E题吧...E题就是首先定义字符串的" * "操作,s*t=t+s1+t+s2+....+sn+t,然后给出n个字符串,最后问最终的字符串中,连续的字符最多有多少个?
感觉这题也还是挺有意思的,可以用递推来解:设f[i][j]表示为前i个字符串,字符j的最长连续字符的个数。那么到第i+1个字符串时,首先求出它的前后缀,然后分情况转移:
1.当前串的所有字符都相同;2.连续的前缀和连续的后缀相等;3.连续的前缀和连续的后缀不相等,这个应该还是比较好求了。
这是从前往后递推的,然后也可以从后往前递归,题解说的是可以证明这个结合律是合法的,然后从后往前,其实思想都差不多。
重要的是自信,一旦有了自信,人就会赢得一切。