Hello 2022 cf题目解析
Hello 2022
真的长时间不打cf手就生了,呜呜呜。
现在已经定下了大致的策略了,所以发誓非必要不跳任何一场cf的比赛。(必要的定义是:我想怎么定义就怎么定义.....)
先看这场吧,就写了三道题,....,写完C后,就剩10几分钟了,也来不及写了...发现简单题还是要提速的,不然真的后面的题都没时间写了。
A. Stable Arrangement of Rooks
第一题就是一道简单的构造题,给定一个n*n的棋盘,往上面放棋子,每个棋子可以攻击到他所在行,所在列的所有网格中的棋子,问给定k个棋子,能不能构造出一种方案,使得任意一个棋子在移动一步后仍是互相不影响的。
这种构造很简单就是依次往(1,1),(3,3),...,这样一直放下去就行。
B. Integers Shop
b题卡了很久,简化题意:给定你n个区间,每个区间有相应的价格,问当依次给定前i个区间时(i=1,2,...,n),覆盖最多数字的最小代价是多少?这里覆盖的定义是你选定的区间的最左端点到最右端点之间的所有数都被覆盖。
首先观察题意可以发现覆盖的个数只和左右端点有关。所以可以肯定的是我们选择的区间最多有两个。一个要求它的左端点足够小,一个要求它的右端点足够大,同时还有可能出现一个区间的情况。我们维护最左端点,最右端点,再来个一个区间的情况即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct tree
{
int l,r,c;
}a[100010];
int main()
{
// freopen("2.in","r",stdin);
int T;scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].c);
int lid=0,rid=0,id=0;
for(int i=1;i<=n;++i)
{
bool fa=0;
int cs=0;
if(lid==0&&rid==0)
{
lid=i;rid=i;
id=i;
cs=a[i].c;
}
else
{
if(a[i].l<a[lid].l) lid=i,fa=1;
if(a[i].l==a[lid].l&&a[i].c<a[lid].c) lid=i;
if(a[i].r>a[rid].r) rid=i,fa=1;
if(a[i].r==a[rid].r&&a[i].c<a[rid].c) rid=i;
if(a[i].l<a[id].l||a[i].r>a[id].r||a[i].l==a[id].l&&a[i].r==a[id].r&&a[i].c<a[id].c) id=i;
int d=a[lid].c+a[rid].c;
if(lid==rid) d/=2;
if(a[id].l==a[lid].l&&a[id].r==a[rid].r) cs=min(d,a[id].c);
else cs=d;
}
printf("%d\n",cs);
}
}
return 0;
}
C. Hidden Permutations
再来看c题,第一道交互题,也是现学现做的。
这种轮换的题,我们可以通过建图的方式,发现他就是一个循环,通过某一个位置的数字就是循环的所有数字,我们找到这个循环,暴力做就行了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct tree
{
int l,r,c;
}a[100010];
int main()
{
// freopen("2.in","r",stdin);
int T;scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].c);
int lid=0,rid=0,id=0;
for(int i=1;i<=n;++i)
{
bool fa=0;
int cs=0;
if(lid==0&&rid==0)
{
lid=i;rid=i;
id=i;
cs=a[i].c;
}
else
{
if(a[i].l<a[lid].l) lid=i,fa=1;
if(a[i].l==a[lid].l&&a[i].c<a[lid].c) lid=i;
if(a[i].r>a[rid].r) rid=i,fa=1;
if(a[i].r==a[rid].r&&a[i].c<a[rid].c) rid=i;
if(a[i].l<a[id].l||a[i].r>a[id].r||a[i].l==a[id].l&&a[i].r==a[id].r&&a[i].c<a[id].c) id=i;
int d=a[lid].c+a[rid].c;
if(lid==rid) d/=2;
if(a[id].l==a[lid].l&&a[id].r==a[rid].r) cs=min(d,a[id].c);
else cs=d;
}
printf("%d\n",cs);
}
}
return 0;
}
D. The Winter Hike
发现人真的不能怕啊,有些题啊,就是看起来很咋呼人,其实想清楚了真的很简单....(我承认我是被这个题吓到了...)
首先右下角的所有雪我们是必移动的,之后考虑移走哪些雪,能让我们到达右下角,我们可以先看一下这些特殊的位置,(1,1),(1,n),(n,1),(n,n).即左上角的四个角。发现无论它们怎么移动,一定会走到一下八个点之一:(1,n+1),(1,2n),(n,n+1),(n,2n),(n+1,1),(n+1,n),(2n,1),(2n,n),即右上和左下的四个角,即这八个角我们是一定要移走一个的。接下来考虑怎么最大限度的利用这些位置。发现,我们完全可以动过某一个点来实现整体的从左上角到右下角。这个题就是思维难度很高,但下限也很低,只要你想到了,基本是必过的........看来当今ACM和OI真的更看重思维能力了.
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll c[510][510];
int main()
{
// freopen("2.in","r",stdin);
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
for(int i=1;i<=2*n;++i)
for(int j=1;j<=2*n;++j) scanf("%d",&c[i][j]);
ll s=1e18;
s=min(s,min(c[1][n+1],c[1][2*n]));
s=min(s,min(c[n][n+1],c[n][2*n]));
s=min(s,min(c[n+1][1],c[n+1][n]));
s=min(s,min(c[2*n][1],c[2*n][n]));
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j) s+=c[i+n][j+n];
printf("%lld\n",s);
}
return 0;
}
E. New School
这个题也是很有意思的。
首先我们可以发现我们其实只需要前m大的老师,其余的老师没用,让这m大的老师去教这m个团队的学生即可。其次如果m个团队的平均成绩都确定的话,我们仍可以发现,可以开始教学的充要条件是\(a_i>=avg_i\)(两者都是从大到小排过序的)。当某个团队的学生退学后,只是相当于某个\(avg_i\)改变了,之后我们重排,重新比较,发现这个似乎很难维护。不如我们将这两个数组放到一起去比较。我们仍按照从大到小的顺序去重排,老师若和学生的平均成绩相同,则老师排前面。这样之后我们可以令数组\(c\)为重排后的数组,这样之后可以开学的充要条件为任意一个前缀中,老师的个数大于等于学生的个数。不如我们将老师视为1,学生视为-1,开学条件就变为任意一个前缀和都大于等于0.接下来我们考虑一下有学生退学造成的影响。假设我们团队i中有人退学,退学后,它的排名在j之后,其实这对于i之前和j之后的前缀和都毫无影响,就i+1到j的前缀和产生了影响。若j在i之后,则这个区间的前缀和,都加一。若j在i之前,则这个区间的前缀和都减一。之后检查这个区间是否符合每一个前缀和都大于等于0的条件即可。
这在具体的实现时,我们可以考虑的更细致一些,由于stl自带的二分的关系,我们都采用从小到大排序的原则(老师和团队的乘积相同的话,老师排在之后),统计后缀的值大于等于0即可。另外,我们可以看一下原本无人退学的情况下,是否可以开课。可以再次分类讨论。若可以开课,则若某个学生退学后,团队的平均成绩反而下降了,则一定可以继续开课。若上升了,则说明只能向后移动,那们就是(i+1,j)这个区间里的所有后缀都减一,由于原本可以开课,则这个区间的任意后缀和都是大于等于0的,那么减一如果造成不开课的话,那么只能说明这个区间有的后缀为0.我们可以用后缀的前缀统计后置0的数量即可判断这个区间内有无后缀为0.接下来考虑原本不能开课的情况,若学生退学之后,团队的成绩反而提高了则一定不能开课。若下降了,则排名只能向前移。那么就是(j,i-1)这个区间的所有数都加1,考虑因为原本就不能开课,则有的前缀和小于0,若某个前缀和<=-2的话,则无论如何都开不了课,这个可以特判掉,这样就只剩下-1和0了,倘若所有的-1都落在上述区间了,则区间加一后,就可以开课了,这个和上述同理,可以用前缀和(后缀的后缀的前缀和...)轻松维护。
#include<bits/stdc++.h>
#define ll long long
#define db double
using namespace std;
const int N=1e5+10;
int T,n,m,a[N],k[N],b[2*N],cnt0[2*N],cnt1[2*N],pre[N];
db avg[N],d[2*N],sum[N];
vector<int>v[N];
struct shu
{
db v;
int id;//0表示老师,1-m表示学生。
}c[N*2];
inline void clear(int m)
{
for(int i=1;i<=2*m+5;++i)
{
c[i].v=0;c[i].id=0;
cnt0[i]=0;cnt1[i]=0;
b[i]=0;
}
for(int i=1;i<=m;++i) v[i].clear(),sum[i]=0;
}
inline bool cmp(shu x,shu y)
{
if(x.v!=y.v) return x.v<y.v;
return x.id>y.id;
}
inline bool check()
{
for(int i=1;i<=2*m;++i) if(b[i]<0) return false;
return true;
}
inline bool check1()
{
for(int i=1;i<=2*m;++i) if(b[i]<=-2) return true;
return false;
}
inline void solve1()//原本可以。
{
for(int i=1;i<=m;++i)
{
int id1=pre[i];
for(auto x:v[i])
{
if((1.0*x)>=avg[i])//平均成绩变小,一定可以
{
printf("1");
continue;
}//平均成绩变大。
db s=(sum[i]-x)/(1.0*(k[i]-1));//求出新的平均值
int id=lower_bound(d+1,d+2*m+1,s)-d;
if(id==2*m+1||cnt0[id1+1]-cnt0[id]>0||b[id]<1) printf("0");
else printf("1");
}
}
}
inline void solve2()//原本不可以。
{
if(check1())
{
for(int i=1;i<=m;++i)
for(int j=1;j<=k[i];++j) printf("0");
return;
}
for(int i=1;i<=m;++i)
{
int id1=pre[i];
for(auto x:v[i])
{
if((1.0*x)<=avg[i])//平均成绩变大,一定不可以
{
printf("0");
continue;
}//平均成绩变小。
db s=(sum[i]-x)/(1.0*(k[i]-1));//求出新的平均值
int id=lower_bound(d+1,d+2*m+1,s)-d;
if(id==id1) printf("0");
else if(cnt1[id]-cnt1[id1+1]==cnt1[1]&&b[id1+1]+b[id]-b[id1]>=1) printf("1");
else printf("0");
}
}
}
int main()
{
// freopen("1.in","r",stdin);
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
clear(m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
sort(a+1,a+n+1);
int tot=0;
for(int i=n;i>=n-m+1;--i) c[++tot].v=a[i],c[tot].id=0;
for(int i=1;i<=m;++i)
{
scanf("%d",&k[i]);
db s=0;
for(int j=1;j<=k[i];++j)
{
int x;scanf("%d",&x);
v[i].push_back(x);
s+=x;
}
sum[i]=s;
s=s/(1.0*k[i]);
avg[i]=s;
c[++tot].v=s;c[tot].id=i;
}
sort(c+1,c+2*m+1,cmp);
for(int i=2*m;i>=1;--i)
{
d[i]=c[i].v;
pre[c[i].id]=i;
b[i]=b[i+1];
if(c[i].id==0) b[i]++;
else b[i]--;
cnt0[i]=cnt0[i+1];
cnt1[i]=cnt1[i+1];
if(b[i]==0) cnt0[i]++;
if(b[i]==-1) cnt1[i]++;
}
if(check()) solve1();
else solve2();
printf("\n");
}
return 0;
}
F. Strange Instructions
首先根据题意我们可以得出最后的操作序列一定是(1,3),2,(1,3),2....这样的或2,(1,3),2,(1,3)...这样的形式。我们发现操作1和操作2都是将在一起的1,0变成一个0,1这很自然的让我们想到将0,1分块,把他们都看成一段一段的。如果只有操作1和操作2的话,那么段与段之间是相互独立的,不会产生影响。但操作三,移走一个0,确实得段与段之间可以相互连接。其次我们发现,如果0不是单独的一个的话,我们会更优先选用操作1,而不用操作3,因为他们对于一段连续的0而言,他们的效用是相同的。对于操作2而言,我们发现我们移走哪段的1,对最终答案根本没有影响。因为最终的一段1一定是只剩下一个1,移走哪的1根本不影响。所以对于操作2而言,我们只关心它能最多操作的次数,和是否有两端1中间的0被移走了,导致两端1合在一起导致操作2的次数增多。