【题解】Codeforces Round #600(Div.2)
Codeforces Round #600(Div.2)
https://codeforces.com/contest/1253/problem
A.Single Push
思路:数组各位相减,得到b-a之后的。如果全为0,或者只有一段非0且数字相同则可行,否则不可行。具体实现的话,可以左右两边指针向中间搜到第一个不为0的数,再判断中间是否均为同一个数。复杂度\(O(n)\)。
注意:多组数据一定要判断是否需要清空。这里我a[n+1]没有清0,结果WA on test55……
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int M=1e5+20;
int a[M];
int main(){
int T;
scanf("%d",&T);
for (int z=1;z<=T;++z){
int n;
scanf("%d",&n);
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
a[n+1]=0;
int c;
for (int i=1;i<=n;++i){
scanf("%d",&c);
a[i]-=c;
}
bool v=true;
int l=1,r=n;
while(l<=n&&a[l]==0)
++l;
while(r>l&&a[r]==0)
--r;
int std=a[l];
if (std>0)
v=false;
else
for (int i=l;i<=r;++i)
if (a[i]!=std){
v=false;
break;
}
if (v)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
B.Silly Mistake
思路:可以用unordered_set记录目前在office里面的人,用unordered_map来记录一个人是否进入过office。对于每个event,如果为正,则判断一下是否之前已经进入过,如果已经进入过则非法,否则就记录这个人为1,并加入set;如果为负,则先判断一下是否在set里,如果没有则非法,否则就删掉这个人,这里再判断一下office是否空了,如果空了就直接当做一天结束(因为题目说天可以随便划分)。复杂度\(O(n)\)。
注意:
- \(O(1)\)清空STL:直接建一个相同的,每次直接st=st0;
UPD:不好意思我假了,直接这样赋值清空比clear还慢,最好的做法是swap(t,t0),这样是O(1)的。不过这样也只能使用一次,所以一般来说就clear就行了。这是多组数据的最优方案。 - 很多情况可以用unordered_set和unordered_map来优化复杂度(去掉log),不过小心被卡掉(哈希)。不过可以想办法保证不被卡掉,大概意思是自己写一个随机化的哈希,详情见neal的博客(cf上)。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int M=2e6+20,P=1e6;
vector<int> vec;
unordered_set<int> st;
unordered_map<int,int> mp,mp0;
int c[M];
int main(){
int n;
scanf("%d",&n);
bool v=true;
for (int i=1;i<=n;++i)
scanf("%d",&c[i]);
for (int i=1;i<=n;++i){
if (c[i]>0){
if (mp[c[i]+P]){
v=false;
break;
}
else
st.insert(c[i]),mp[c[i]+P]=1;
}
else{
if (st.count(-c[i])){
st.erase(-c[i]);
if (st.empty())
vec.push_back(i),mp=mp0;
}
else{
v=false;
break;
}
}
}
if (!st.empty())
v=false;
if (!v)
printf("-1\n");
else{
printf("%d\n%d",vec.size(),vec[0]);
for(int i=1;i<vec.size();++i)
printf(" %d",vec[i]-vec[i-1]);
}
return 0;
}
C.Sweets Eating
思路:首先贪心很容易想到,每次总是先吃\(a_i\)更大的糖,这样总penalty最少。但题目要求输出k=1-m的所有情况。举几个例子之后可以发现,只要从小到大排序,那么k每增加1,答案会加上排序后的\(a[k\%m],a[k\%m+m],a[k\%m+2m]……\),下标不大于k。那么只要先排个序,之后再预处理每个\(k\%m\)的前缀和,再扫一遍输出答案即可。复杂度\(O(nlogn)\)。
注意:不开LL见祖宗。
AC代码:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int M=2e5+20;
int a[M];
vector<LL> mmd[M];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
for(int i=0;i<m;++i){
int j=1;
mmd[i].push_back(a[i]);
while(j*m+i<=n)
mmd[i].push_back(mmd[i][j-1]+a[j*m+i]),++j;
}
LL ans=a[1];
printf("%lld",ans);
for (int i=2;i<=n;++i){
ans+=mmd[i%m][i/m];
printf(" %lld",ans);
}
putchar('\n');
return 0;
}
D.Harmonious Graph
思路:这道题可以发现,题目的描述等价于:如果l到r有一条路径,那么l,r之间所有点都必须是连通的。那么我们可以想到用并查集(dsu)来处理。
首先遍历每一条边进行合并,之后再扫一遍将每个集合找出来,记录每个集合的元素个数cnt,最小点序号mn和最大点序号mx。
如果mx-mn+1=cnt,那么说明这个集合中的元素为mn\(\sim\)mx,满足了题意条件。
如果mx-mn+1<cnt,那么说明有些点在其他的集合里,我们必须要将这两个集合连起来才可以把缺失的点放进来。那么连接两个集合只需要添加一条边,直接答案加1即可。
当然,连接完之后的集合变大了,可能仍然不满足mx-mn+1=cnt,那么就继续上述步骤直到满足了该条件为止。当所有的集合都满足条件之后,就输出答案即可。
那么怎么来找到缺失的点所在的集合呢?我们对每个并查集只记录cnt,mn,mx,将所有并查集按照mn从小到大排序。若第i个集合不满足条件,那么肯定会缺失第i+1个集合的mn点,就将i和i+1集合合并,如果不满足就继续合并,直到满足了为止。这可以用优先队列来实现。
复杂度为并查集和优先队列的\(O(nlogn)\),实现常数有点大,不过140ms过掉了。
注意:好像有\(O(n+m)\)的做法……回头看Tutorial吧。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int M=2e5+20;
int road[M];
int fnd(int x){
return (x==road[x])?x:road[x]=fnd(road[x]);
}
void merge(int u,int v){
road[fnd(u)]=fnd(v);
}
struct Bcj{
int mn,mx,cnt;
Bcj(int a=1e9+7,int b=0,int c=0):mn(a),mx(b),cnt(c){}
void proc(int i){
mn=min(mn,i);
mx=max(mx,i);
++cnt;
}
void clear(){
mn=1e9+7;
mx=cnt=0;
}
void merg(Bcj x){
mn=min(mn,x.mn);
mx=max(mx,x.mx);
cnt+=x.cnt;
}
}bcj[M];
struct cmp{
bool operator()(Bcj x,Bcj y){
return x.mn>y.mn;
}
};
priority_queue<Bcj,vector<Bcj>,cmp> q;
int main(){
int n,m;
scanf("%d%d",&n,&m);
int a,b;
for (int i=1;i<=n;++i)
road[i]=i;
for (int i=1;i<=m;++i){
scanf("%d%d",&a,&b);
merge(a,b);
}
for(int i=1;i<=n;++i)
bcj[i].mx=bcj[i].mn=i,bcj[i].cnt=1;
for(int i=1;i<=n;++i)
if (i!=fnd(i)&&bcj[i].mx)
bcj[fnd(i)].proc(i),bcj[i].clear();
//for(int i=1;i<=n;++i)
// cout<<bcj[i].mn<<' '<<bcj[i].mx<<bcj[i].cnt<<endl;
for(int i=1;i<=n;++i)
if (bcj[i].mx)
/*cout<<bcj[i].mn<<' '<<bcj[i].mx<<' '<<bcj[i].cnt<<endl,*/q.push(bcj[i]);
Bcj prs;
int ans=0;
while(!q.empty()){
Bcj prs=q.top();
q.pop();
while(prs.cnt!=prs.mx-prs.mn+1){
prs.merg(q.top());
q.pop();
++ans;
}
}
printf("%d\n",ans);
return 0;
}
E.Antenna Coverage
题意:一条街道m个点[1,m],有n个发射站,每个发射站有两个参数\(x_i\)和\(s_i\),表示发射站位于\(x_i\),能覆盖\([x_i-s_i,s_i+s_i]\)。我们可以花1coin将每个发射站的\(s_i\)增加1,问最少花费多少coins能够完全覆盖[1,m]。保证两个发射站不会位于同一个地点。\(1\leq n\leq 80\),\(n\leq m\leq 100000\)。
思路:VP的时候看数据范围猜到了应该是dp,但没想到怎么转移。实际上应该不算很难想。设dp[i]表示覆盖[1,i]的最小花费,那么转移方式有两种:
- 从[1,i-1]的方案直接转移过来。如果i处已经被覆盖了,那么dp[i]=dp[i-1];如果i处没有被覆盖,那么就让覆盖了i-1的那个基站再扩大1,有dp[i]=dp[i-1]+1。
- 从右端点<=i的基站转移过来。有dp[i]=min(dp[i],i-ant[j].r+dp[max(2*ant[j].x-i-1,0)])。其中ant[j]表示第j个基站,r表示基站的\(x_j+s_j\)。这一步要枚举所有符合条件的基站,转移复杂度为\(O(n)\)。
答案为dp[m],判断是否被覆盖需要预先对[1,m]的区间建立vis数组预处理,最坏复杂度是\(O(nm)\),dp复杂度也是\(O(mn)\),所以总复杂度是\(O(nm)\)。
注意:
- 要增加一个(x=0,s=0)的基站。由于用这个基站去覆盖任何区间的方案都是最差的,因此不会影响答案(每次都取最小值)。
- 注意预处理和dp的时候不要越界,左侧最小是0。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int M=1e5+20;
struct Ant{
int x,s,l,r;
Ant(int a=0,int b=0,int c=0,int d=0):x(a),s(b),l(c),r(d){}
bool operator<(Ant x){
return r<x.r;
}
}ant[100];
int dp[M],vis[M];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i){
int x,s;
scanf("%d%d",&x,&s);
ant[i]=Ant(x,s,x-s,x+s);
for(int j=max(0,x-s);j<=min(m,x+s);++j)
vis[j]=1;
}
dp[0]=0;
for (int i=1;i<=m;++i){
if (vis[i])
dp[i]=dp[i-1];
else
dp[i]=dp[i-1]+1;
for (int j=1;j<=n;++j)
if (ant[j].r<=i)
dp[i]=min(dp[i],i-ant[j].r+dp[max(2*ant[j].x-i-1,0)]);
}
printf("%d\n",dp[m]);
return 0;
}
总结
这次把D题做出来了,虽然ABC代码实现和大佬们差不多,但感觉前面写的老有问题,写的有点慢,而且ABC各WA一发,说明写代码的风格和习惯还是不太好,D题想的比较快,表扬自己。E题没想出来其实不太应该,不过当时也是不太想写了。明晚cf争取上1800。