【解题报告】CF DIV2 #ROUND 709 A~D
比赛链接
当开到这场的时候我意识到,这场游戏的性质已经变了。无奖竞猜我会连着掉分多少次
A. Prison Break
思路
一眼水题,然而开局网络爆炸,等了好久才交成
代码
#include<bits/stdc++.h>
using namespace std;
int T;
int main()
{
cin>>T;
while(T--)
{
int a,b;
cin>>a>>b;
cout<<a*b<<endl;
}
return 0;
}
B. Restore Modulo
思路
数论+分类讨论+模拟
不知道为啥,貌似最近老被取模题整。
参考了这位大佬的题解
读题读题读题!!!数据范围数据范围数据范围!!!
看到最后一条输出的提示了吗,c是<m的。这意味着在最后一类情况中
a
i
=
(
a
i
−
1
+
c
)
m
o
d
m
a_i=(a_{i-1}+c)\mod m
ai=(ai−1+c)modm可以转化为如两种情况
A:如果
a
i
−
1
+
c
>
=
m
a_{i-1}+c>=m
ai−1+c>=m那么
a
i
=
a
i
−
1
+
(
c
−
m
)
a_i=a_{i-1}+(c-m)
ai=ai−1+(c−m),且由于
c
−
m
<
=
0
c-m<=0
c−m<=0,所以
a
i
<
=
a
i
−
1
a_i<=a_{i-1}
ai<=ai−1
B:如果
a
i
−
1
+
c
<
m
a_{i-1}+c<m
ai−1+c<m那么
a
i
=
a
i
−
1
+
c
a_i=a_{i-1}+c
ai=ai−1+c,且
a
i
>
a
i
−
1
a_i>a_{i-1}
ai>ai−1
对于给出的序列我们可以分成以下这些情况
①一直满足B,即
a
i
=
a
i
−
1
+
c
a_i=a_{i-1}+c
ai=ai−1+c一直成立,恒定增加,差值为定值,那么m可以无限大输出0
1 2 3 4 5
a1+c<m
a2+c<m
a3+c<m
a4+c<m
m>max(a1,a2,a3,a4)+c即m>a1+c
②一直满足A,即 a i = a i − 1 + ( c − m ) a_i=a_{i-1}+(c-m) ai=ai−1+(c−m)一直成立,恒定减少,差值为定值,那么m最大为 m = a n − 1 + c − 1 m=a_{n-1}+c-1 m=an−1+c−1所以也输出0,如果差值减少的差值并不相等那么输出-1(之前没考虑的)
5 4 3 2 1
a1+c>=m
a2+c>=m
a3+c>=m
a4+c>=m
m<=min(a1,a2,a3,a4)+c即m<a4+c
然而这并不正确,如果所有数都相等的话,
③既有满足A的,也有满足B的
我们可以通过恒定递增的部分来确定c的值,如果c是不唯一的,那么直接输出-1。
例如下面的
C不唯一的情况
1 3 5 7 4 1 10 19
a1+c<m
a2+c<m
a3+c<m
a4+c>=m
a5+c>=m
a6+c<m
a7+c<m
如果C唯一,那么我们可以通过下降段来求出m,如果m不唯一那么输出-1,如果唯一输出m
(原本这里以为是要用不等关系的约束条件来搞,结果暴毙)
求m
1 9 17 6 14 3
c=8
17+8-m=6
m=(17-6)+8
-差 c
然而如果按照上面描述写代码的话会发现还是会WA,还需要补充以下两点。如果n为1的话输出0。如果得出的m>=max(a[i])输出-1。补充上这两点后就可以AC啦!
代码
#include<bits/stdc++.h>
using namespace std;
int T;
const int N=1e5+10;
int a[N],d[N];
int main()
{
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
int maxa=-1;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
maxa=max(maxa,a[i]);
}
if(n==1)puts("0");
else
{
set<int>sa,sb;
sa.clear(),sb.clear();
for(int i=1;i<n;i++)
{
d[i]=a[i]-a[i-1];
if(d[i]>=0)sa.insert(d[i]);
else sb.insert(d[i]);
}
int siza=sa.size(),sizb=sb.size();
if(siza==1&&sizb==0)puts("0");
else if(siza==0&&sizb==1)puts("0");
else if(siza>1||sizb>1)puts("-1");
else if(siza==1&&sizb==1)
{
int c=*sa.begin();
int m=c-*sb.begin();
if(m<=c||m<=maxa)puts("-1");
else printf("%d %d\n",m,c);
}
}
}
return 0;
}
C. Basic Diplomacy
题意
有n个朋友,总共m天,每天选择一个和一个朋友玩,每个朋友总选择次数不能超过
⌈
m
2
⌉
\lceil\frac m 2\rceil
⌈2m⌉次,现在给出m天每天可以选择朋友。如果有可行方案输出YES和满足条件的选择方式。如果不满足就输出NO
思路
贪心题。
比赛的时候稍微看了几眼,首先想到的是只有一个的选择的要先选,那么衍生出一个贪心就是先可选人数从小到大排序,然后每天选剩余可选次数最多的那个人。
比赛的时候正解思路差不多就是这样了,然后死在了代码实现。
原本一直在想用堆求最值,实际上总的 k i k_i ki是比较小的,所以直接暴力+映射就完事啦!
赛后一发秒了,离谱!
代码实现有些技巧,因为N是2e5级别的所以直接开二维数组是会爆空间的,所以利用vector数组来实现。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef pair<int,int> PII;
int T;
vector<PII>v[N];
int t[N];//统计用了多少
int ans[N];
bool cmp(vector<PII>a,vector<PII>b)
{
if(a.size()<b.size())return 1;
return 0;
}
bool cmp_2(PII a,PII b)
{
int x=a.second,y=b.second;
if(t[x]<t[y])return 1;
return 0;
}
int main()
{
scanf("%d",&T);
while(T--)
{
int n,m;
scanf("%d%d",&n,&m);
memset(t,0,sizeof t);
memset(ans,0,sizeof ans);
memset(v,0,sizeof v);
for(int i=1;i<=m;i++)
{
int k;
scanf("%d",&k);
for(int j=0,x;j<k;j++)
{
scanf("%d",&x);
v[i].push_back(make_pair(i,x));//v[i]表示第i天
}
}
sort(v+1,v+1+m,cmp);
bool flag=1;;
for(int i=1;i<=m;i++)
{
sort(v[i].begin(),v[i].end(),cmp_2);
PII tmp=*v[i].begin();//tmp为使用次数最小的
int day=t[tmp.second];//用了多少天
if(day+1>(m+1)/2)
{
puts("NO");
flag=0;
break;
}
else
{
ans[tmp.first]=tmp.second;//第几天=选第几个人
t[tmp.second]++;//选择的这个人使用次数+1
}
}
if(flag)
{
puts("YES");
for(int i=1;i<=m;i++)printf("%d ",ans[i]);
printf("\n");
}
}
return 0;
}
反思
A:
一眼题,唯一的希望就是校园网能快一点(卑微)
B:
注意数据范围
当c<mod的时候我们可以吧取模操作直接转化为±操作。
分类讨论的思想:A,B两种情况,我们可以讨论只有A,只有B,AB混合
注意观察数据范围尤其是0,1这种边界数据
注意检测结果的合法性
C:
看清楚总的数据量,不一定要最优化,有时候暴力整一遍是可以过的
求最值:暴力扫一遍,排序取头,利用堆(cf一般用不上啥数据结构)而且堆的特点是动态调整,你没事用堆干嘛,排序不香吗
可用次数最多的转化为用的最少的,然后直接记录使用次数这样比记录剩余可用次数要方便很多
如果开数组爆空间的话可以考虑开vector
贪心思想:先安排限制严格的,比如非选这个不可。或者考虑先剩余可利用次数多的。