Codeforces Round #481 (Div. 3) 解题报告
A - Remove Duplicates
题目大意:
现在有一个长度为n的一串数字,要将其中相同的数字删掉。删除的规则:如果数字相同时保留最右边的那一个。输出删除之后的数字串
大致思路:
暴力模拟情况就好···虽然我感觉我写的非常复杂,应该有更简单的写法。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1010;
int a[55];
struct Node{
int pos,id;
Node(){pos=0;id=0;}
}pos[maxn];
bool visit[maxn];
bool cmp(Node a,Node b)
{
return a.pos<b.pos;
}
int main()
{
ios::sync_with_stdio(false);
memset(visit,false,sizeof(visit));
int n,cnt=0;
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
if(pos[a[i]].pos==0)
cnt++; //数有多少个数字
pos[a[i]].pos = i;//记录某一个数出现的最后位置
pos[a[i]].id = a[i];
}
vector<Node> q;
cout<<cnt<<endl;
for(int i=1;i<=n;++i){//把结果放到数组里就可以了
if(visit[a[i]]==false){
q.push_back(pos[a[i]]);
//cout<<pos[a[i]].pos<<" "<<pos[a[i]].id<<endl;
visit[a[i]]=true;
}
}
sort(q.begin(),q.end(),cmp);
for(int i=0;i<cnt;++i)
cout<<q[i].id<<" ";
cout<<endl;
return 0;
}
B - File Name
题目大意:
现在有一个长度为n的字符串,现在想让字符串中不存在连续的三个x字符。问需要删除多少个字符。
大致思路:
也是暴力模拟就好,循环里一个计数器,看连续的x的个数
代码:
#include<bits/stdc++.h>
using namespace std;
char str[110];
int main()
{
int n,ans=0,cnt=0;
cin>>n>>str;
for(int i=0;i<n;++i){
//cout<<i<<" "<<cnt<<" "<<ans<<endl;
if(str[i]!='x') cnt=0;
else cnt++;
if(cnt==3){
ans++;
cnt--;
}
}
//cout<<str<<endl;
cout<<ans<<endl;
return 0;
}
C - Letters
题目大意:
题目背景比较复杂,现在简化一下:
有 \(n\) 个房间,按 $ 1...n$ 进行编号, 每个房间里有 \(a_i\) 个格子。
有 \(m\) 个询问,每个询问给出一个数字,代表着我现在要找的是第 \(x\) 个格子
这个 \(x\) 是对于全局的。比如我现在第一个房间里有10个,第二个房间里有10个。那么第二个房间里的格子的序号就是从 \(11...20\)
要求对于每一个询问,输出这个格子所在的房间号和这个格子相对于这个房间的序号。
解题思路:
对于 \(a_i\) 求一个前缀和。对于每一个询问在前缀和里面进行二分,答案就很显然的出来了。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+7;
ll a[maxn],b[maxn],pre[maxn]={0};
int main()
{
ios::sync_with_stdio(false);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1;i<=m;++i)
cin>>b[i];
for(int i=1;i<=n;++i)
pre[i] = a[i]+pre[i-1];
for(int i=1;i<=m;++i){
int pos = lower_bound(pre+1,pre+1+n,b[i]) -(pre+1);
cout<<pos+1<<" "<<b[i] - pre[pos]<<endl;
}
return 0;
}
吐槽一下:这个题给了4s,是想让 \(O(nm)\) 的暴力也能过吗···
D - Almost Arithmetic Progression
题目大意:
有一个长度为 \(n\) 的数字序列,你可以对每一个数字进行一次 \(+1\) 或者 \(-1\) 的操作。
使得这个序列变成一个等差序列。现在求最小的操作次数,如果不能变成等差序列,就输出 \(-1\)
解题思路:
对于整个序列枚举公差就好了。
先将数列的最大值和最小值得到,假设为 \(maxx\) 和 \(minl\) 。
现在设公差为 \(d\) ,序列长度为 \(n\) 。
那么可以很显然的得到:
\(d\times (n-1) = (maxx-minl-2) \ldots (maxx-minl+2)\)
然后这个题有一个 \(trick\) ,就是公差可以是负数,所以就需要扫两次。正向一个,反向一次
这个算法的最坏情况下就是 \(2\times 4\times 3 \times n\) 次循环,也就是 \(2e6\) 。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF =1<<30;
const int maxn=1e5+7;
int a[maxn];
int main()
{
//freopen("in.txt","r",stdin);
ios::sync_with_stdio(false);
int n;
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i];
if(n<=2){
cout<<0<<endl;
return 0;
}
int maxx = -INF,minl = INF,ans=1<<30;
for(int i=1;i<=n;++i)
maxx = max(maxx,a[i]),minl = min(minl,a[i]);
//cout<<maxx<<" "<<minl<<endl;
int d;
bool flag=false;
for(int i=maxx-minl-2;i<=maxx-minl+2;++i){ //正向
if(i%(n-1)==0){
d=i/(n-1); //枚举公差
ll now;
//cout<<d<<endl;
for(int i=-1;i<=1;++i){
int cnt=0,x;
now = a[1] +i;
for(int i=1;i<=n;++i){
x=abs(now-a[i]);
if(x>1){now=0;break;}
if(x!=0) cnt++;
now+= d;
}
if(now){
//cout<<d<<" "<<a[1]+i<<endl;
ans =min(ans,cnt);
flag=true;
}
}
}
}
if(!flag)
for(int i=minl-maxx-2;i<=minl-maxx+2;++i){ //反向
if(i%(n-1)==0){
d=i/(n-1);
ll now;
//cout<<d<<endl;
for(int i=-1;i<=1;++i){
int cnt=0,x;
now = a[1] +i;
for(int i=1;i<=n;++i){
x=abs(now-a[i]);
if(x>1){now=0;break;}
if(x!=0) cnt++;
now+= d;
}
if(now){
//cout<<d<<" "<<a[1]+i<<endl;
ans =min(ans,cnt);
flag=true;
}
}
}
}
if(flag)
cout<<ans<<endl;
else
cout<<-1<<endl;
return 0;
}
E - Bus Video System
题目大意:
有一辆公交车,现在知道连续 \(n\) 个站的上下人数之和,还有车的容量。
问这个车在开到这n个站之前的人数可能情况有多少种。
解题思路:
将情况简单模拟一下,就可以知道有以下的规律:
在这 \(n\) 个站的过程中,出现的最大数大于0,说明之前肯定有至少有这么多的空位。
若出现的最小数<0,则说明之前肯定至少有这么多人已经在车上了。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF =1<<30;
int main()
{
int n,w,num;
while(cin>>n>>w)
{
int now=0,maxx=-INF,minl=INF;
for(int i=0;i<n;++i){
cin>>num;
now+=num;
maxx = max(maxx,now);
minl = min(minl,now);
}
int ans = w+1;
if(minl!=-INF && minl<0)
ans+=(minl);
if(maxx!=INF && maxx >0)
ans-=(maxx);
if(ans<0)
ans=0;
//cout<<maxx<<" "<<minl<<endl;
cout<<ans<<endl;
}
return 0;
}
F - Mentors
题目大意:
有 \(n\) 个人,每一个人有一个技能值 \(r\) 。\(m\) 对人之间有争吵。现在定义一个关系:教师
\(\forall a,b \in n , r_a > r_b\) 且 \(a\) 与 \(b\) 之间没有争吵,那么 \(a\) 可以做 \(b\) 的教师
现在问对于每一个人,能做多少个人的教师?
解题思路:
给每一个人建一个set。
意义为:\(\forall x \in set[a] , r_a > r_x\)
也就是说 \(a\) 可以做 \(x\) 的教师
然后把每个人的信息按技能值排序。到时候看排序后的位置减去 \(set\) 的大小就可以了。
但这里还涉及到一个问题:存在多个人技能值相同 。
也就是说不能直接按照排序后的位置直接当成答案,还需要去找到第一个比自己技能值小的人的位置。
最简单暴力的方法就是每一次循环暴力找
如我第一次提交的代码:
for(int i=n;i>=1;--i){
int j=i-1;
while(pro[i].r<=pro[j].r) //暴力寻找第一个比i小的位置
j--;
ans[pro[i].id] = j - st[pro[i].id].size();
}
但这样会很容易的被数据卡主,比如当所有的人的技能值都一样的时候。直接退化成一个 \(O(n^2)\) 的循环, \(2e^5\) 的数据范围肯定会炸。
改良方法
利用一个辅助数组,预处理最近一个比他小的位置。光用文字不是很好说,看一下代码然后手动模拟一遍应该就清楚了。
int cnt=1,pre=1;
Node now; // Node类型存的是人的信息
for(int i=1;i<=n;++i){
if(pro[i].r==now.r)
cnt++;
else{
now = pro[i];
cnt =1;
}
father[i] -=cnt;
}
然后我们在循环中就可以直接使用这个数组了
for(int i=n;i>=1;--i)
ans[pro[i].id] = (i+father[i]) - st[pro[i].id].size();
这样这个循环就是一个稳定的 \(O(n)\) 了
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+7;
struct Node{
int r,id;
Node(){r=id=0;}
}pro[maxn];
set<int> st[maxn];
int ans[maxn]={0},father[maxn]={0};
bool cmp(Node a,Node b)
{
if(a.r==b.r)
return a.id<b.id;
return a.r<b.r;
}
int main()
{
//freopen("in.txt","r",stdin);
ios::sync_with_stdio(false);
int n,k,x,y;
cin>>n>>k;
for(int i=1;i<=n;++i){
cin>>pro[i].r;
pro[i].id=i;
}
for(int i=1;i<=k;++i){
cin>>x>>y;
if(pro[x].r>pro[y].r)
st[x].insert(y);
else if(pro[x].r<pro[y].r)
st[y].insert(x);
}
/*for(int i=1;i<=n;++i){
cout<<i<<" ";
for(set<int>::iterator it=st[i].begin();it!=st[i].end();++it)
cout<<*it<<" ";
cout<<endl;
}
cout<<endl;*/
sort(pro+1,pro+1+n,cmp);
int cnt=1,pre=1;
Node now;
for(int i=1;i<=n;++i){
if(pro[i].r==now.r)
cnt++;
else{
now = pro[i];
cnt =1;
}
father[i] -=cnt;
}
/*for(int i=1;i<=n;++i)
cout<<father[i]<<" ";
cout<<endl;*/
for(int i=n;i>=1;--i){
ans[pro[i].id] = (i+father[i]) - st[pro[i].id].size();
}
for(int i=1;i<=n;++i)
cout<<ans[i]<<" ";
cout<<endl;
return 0;
}
G - Petya's Exams
题目大意:
有 \(n\) 场考试,一共 \(m\) 天。每一场考试都有三个参数:\(s\) 通知有这场考试的那一天, \(d\) 考试的那一天,\(c\) 复习所需要的天数
对于每一场考试,都必须复习足够的天数才能满足不挂科。
现在让你输出让所有考试都能顺利通过的时间安排,如果解不存在,输出 \(-1\)
数据保证每一场考试都不在同一天
(有SPJ,所以只要输出合法即可)
解题思路:
贪心的思想。
按每场考试结束的时间进行排序,然后从越早结束的考试开始,然后同考试通知的日期开始复习,直到天数足够。这样安排的过程中,如果出现日期冲突则说明解不存在。
(我是根据样例发现的规律,感觉是没毛病的。但我不会证明。)
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=110;
struct Node{
int s,d,c,id;
}exam[maxn];
int ans[maxn]={0};
bool cmp(Node a,Node b)
{
return a.d<b.d;
}
int main()
{
//freopen("in.txt","r",stdin);
ios::sync_with_stdio(false);
int n,m;
cin>>n>>m;
for(int i=1;i<=m;++i){
cin>>exam[i].s>>exam[i].d>>exam[i].c;
exam[i].id=i;
ans[exam[i].d] = m+1;
}
sort(exam+1,exam+1+m,cmp);
bool flag=false;
for(int i=1;i<=m;++i){
int now = exam[i].s,cnt=0;
while(cnt<exam[i].c&&now<exam[i].d)
{
//cout<<now<<endl;
if(ans[now]==0){
ans[now] =exam[i].id;
cnt++;
}
now ++ ;
}
if(cnt<exam[i].c){
//cout<<exam[i].id<<endl;
flag=true;
break;
}
}
if(flag)
cout<<-1<<endl;
else{
for(int i=1;i<=n;++i)
cout<<ans[i]<<" ";
}
return 0;
}