Educational Codeforces Round 150 (Rated for Div. 2)
Preface
补题,这样一来考试周欠下的CF就都补完了,接下来再把落下的Atcoder补一下就差不多了
这场感觉题目不是很难啊,尤其是E题是真的一眼,但F要用比较高阶的东西,闵可夫斯基和好像之前OI的时候看过但已经忘光光了的说
A. Game with Board
首先容易发现当\(n\ge 5\)时先手必胜,因为此时先手只要对\(n-2\)个\(1\)进行操作即可
否则手玩一下发现其它情况都是后手胜
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t,n;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t) scanf("%d",&n),puts(n<=4?"Bob":"Alice");
return 0;
}
B. Keep it Beautiful
不难发现\(a_1,a_2,\cdots,a_k\)能满足要求的充要条件就是不满足\(a_i\le a_{i+1},i\in[1,k-1]\)以及\(a_k\le a_1\)的位置至多只有一个
那么直接模拟一下即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,a[N],b[N],cnt,num;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
if (n<=2)
{
for (i=1;i<=n;++i) putchar('1'); putchar('\n');
continue;
}
b[1]=a[1]; b[2]=a[2]; putchar('1'); putchar('1');
for (cnt=2,num=b[1]!=b[2],i=3;i<=n;++i)
if (num-(b[cnt]>b[1])+(b[cnt]>a[i])+(a[i]>b[1])>1) putchar('0'); else
num+=-(b[cnt]>b[1])+(b[cnt]>a[i])+(a[i]>b[1]),b[++cnt]=a[i],putchar('1');
putchar('\n');
}
return 0;
}
C. Ranom Numbers
不难发现对于值为\(ch\)的所有位置,最优的操作点一定是它第一次和最后一次出现的位置
因为如果我们要把某个字符改大,那肯定是在开头处改,这样可以减少对中间那段的影响
同理如果我们想把某个字符改小,那肯定是在结尾处改,这样可以增大对中间那段的影响
因此找出所有可能的位置然后枚举变成什么字符,再暴力检验即可
#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,pos[N],cnt,ans,pw[5]; char s[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j,k; for (pw[0]=i=1;i<5;++i) pw[i]=10*pw[i-1];
for (scanf("%d",&t);t;--t)
{
for (scanf("%s",s+1),n=strlen(s+1),cnt=0,j=0;j<5;++j)
{
for (i=1;i<=n;++i) if (s[i]-'A'==j) { pos[++cnt]=i; break; }
for (i=n;i>=1;--i) if (s[i]-'A'==j) { pos[++cnt]=i; break; }
}
for (ans=-2e9,k=1;k<=cnt;++k) for (j=0;j<5;++j)
{
int tmp=s[pos[k]],ret=0,suf[5];
for (s[pos[k]]=j+'A',i=0;i<5;++i) suf[i]=0;
for (i=n;i>=1;--i)
{
ret+=pw[s[i]-'A']*(suf[s[i]-'A']?-1:1);
for (RI t=0;t<s[i]-'A';++t) suf[t]=1;
}
ans=max(ans,ret); s[pos[k]]=tmp;
}
printf("%d\n",ans);
}
return 0;
}
D. Pairs of Segments
刚开始想了一个比较复杂的\(O(n^2\log n)\)的做法,然后写了一半发现有一种更trivial的\(O(n^2)\)做法,遂立刻跑路
首先我们可以\(O(n^2)\)暴力枚举每对区间\([l_1,r_1],[l_2,r_2]\),若\([l_1,r_1]\cap[l_2,r_2]\ne \emptyset\),则我们把\([l_1,r_1]\cup[l_2,r_2]\)记录下来
这样就得到了\(O(n^2)\)个区间,现在就是要在这些新的区间中选出尽量多个使得它们两两不相交
我们把新的区间按照右端点排序后,考虑用DP处理,设\(f_i\)表示处理了前\(i\)个区间后最多能选出多少个区间不交
则转移就是枚举之前的区间\(j\),若\(r_j<l_i\)则可以转移过来,用线段树优化一下做到\(O(n^2\log n)\)
但其实由于这题的值域在离散化之后很小,我们完全可以省去排序和线段树的\(\log\)
直接把所有新区间的左端点插到右端点对应的vector
里,然后做完某个右端点后求一个前缀最大值用于转移即可
具体实现可以看代码,复杂度\(O(n^2)\)
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=4005;
int t,n,l[N],r[N],cnt,rst[N],f[N],g[N]; vector <int> pos[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d",&n),cnt=0,i=1;i<=n;++i)
scanf("%d%d",&l[i],&r[i]),rst[++cnt]=l[i],rst[++cnt]=r[i];
sort(rst+1,rst+cnt+1); cnt=unique(rst+1,rst+cnt+1)-rst-1;
auto find=[&](CI x)
{
return lower_bound(rst+1,rst+cnt+1,x)-rst;
};
for (i=1;i<=n;++i) l[i]=find(l[i]),r[i]=find(r[i]);
for (i=1;i<=cnt;++i) pos[i].clear();
for (i=1;i<=n;++i) for (j=i+1;j<=n;++j)
if (max(l[i],l[j])<=min(r[i],r[j]))
pos[max(r[i],r[j])].push_back(min(l[i],l[j]));
for (i=1;i<=cnt;++i)
{
f[i]=0; for (int j:pos[i]) f[i]=max(f[i],g[j-1]+1);
for (g[i]=f[i],j=1;j<i;++j) g[i]=max(g[i],g[j]);
}
printf("%d\n",n-2*g[cnt]);
}
return 0;
}
E. Fill the Matrix
丁真题,不难发现如果我们能求出每个长度的区间出现的次数,则只要贪心地优先选大的区间一定是最优的
而要维护这个也很容易,直接从下往上枚举每一行,用set
维护一下当前的区间以及出现时间
对于每个断开的位置找到它对应的原区间,计算这段区间贡献的时间长度,然后分成两个小区间即可
然后就做完了,复杂度\(O(n\log n)\)
#include<cstdio>
#include<iostream>
#include<set>
#include<utility>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
int t,n,a[N]; long long m,ans,len[N]; vector <int> h[N]; set <pair <pi,int>> s;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) h[i].clear(),len[i]=0;
for (i=1;i<=n;++i) scanf("%d",&a[i]),h[a[i]].push_back(i);
scanf("%lld",&m); s.clear(); s.insert(mp(pi(1,n),n));
for (ans=0,i=n;i>=1&&m;--i)
{
for (int x:h[i])
{
auto it=s.upper_bound(mp(pi(x,n),n));
if (it==s.begin()) continue;
--it; int l=it->fi.fi,r=it->fi.se,tim=it->se;
s.erase(it); len[r-l+1]+=tim-i;
if (l<=x-1) s.insert(mp(pi(l,x-1),i));
if (x+1<=r) s.insert(mp(pi(x+1,r),i));
}
}
for (auto [itv,tim]:s) len[itv.se-itv.fi+1]+=tim;
//for (i=1;i<=n;++i) printf("%d %d\n",i,len[i]);
for (i=n;i>1&&m;--i)
if (len[i]*i<=m) m-=len[i]*i,ans+=len[i]*(i-1); else
{
long long d=m/i,r=m%i; m-=d*i; ans+=d*(i-1);
if (r>=2) ans+=r-1; m=0;
}
printf("%lld\n",ans);
}
return 0;
}
F. Monocarp and a Strategic Game
首先不妨设最后每种人种的个数为\(sa,sb,sc,sc\),则手玩一下答案的式子就是:
然后我们考虑如果设向量\(v_i=(a_i-b_i,c_i-d_i)\),题目所要求的就是取出若干个向量,以\((0,0)\)为起点,最后要使得它们的和对应的点到原点的距离最远
冷静思考一波,如果现在已经给定了若干个点,要求要原点的距离最大的点,则可能的取值一定在凸包的边界点上
考虑如果我们已经维护了某个凸包表示当前可能为答案的点,此时又加入了一个向量\(v_i\)
那么若选择把这个\(v_i\)加入答案,则相当于将整个凸包向\(v_i\)方向平移,这其实就是个闵可夫斯基和的形式
而如果不把\(v_i\)加入答案呢,我们就套路地对凸包再做一个\(-v_i\)方向的平移来抵消之前平移的影响
而根据闵可夫斯基和对于凸包的可拆解性,我们可以先分别对\(v_i\)和\(-v_i\)作出凸包后,再求凸包的闵可夫斯基和即可
两个凸包的闵可夫斯基和就是由凸包的所有边极角排序后然后顺次连接得到,不过要注意确定起点的坐标
然后这题就做完了,总复杂度\(O(n\log n)\),注意数据范围需要开__int128
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
typedef __int128 i128;
const int N=300005;
inline int quad(CI x,CI y)
{
if (!x&&!y) return 0; else
if (x>0&&y>=0) return 1; else
if (x<=0&&y>0) return 2; else
if (x<0&&y<=0) return 3; return 4;
}
struct Point
{
int x,y,d;
inline Point(CI X=0,CI Y=0)
{
x=X; y=Y; d=quad(X,Y);
}
friend inline bool operator < (const Point& A,const Point& B)
{
if (A.d!=B.d) return A.d<B.d;
return (i128)A.x*B.y>(i128)A.y*B.x;
}
}p[N<<2]; int n,a,b,c,d,cnt;
inline void print(const i128& x)
{
if (x>9) print(x/10); putchar(x%10+'0');
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; i128 sx=0,sy=0; for (scanf("%d",&n),i=1;i<=n;++i)
{
scanf("%d%d%d%d",&a,&b,&c,&d); int vx=a-b,vy=c-d;
if (!vx&&!vy) continue;
p[++cnt]=Point(vx,vy); p[++cnt]=Point(-vx,-vy);
if (vy<0||(vy==0&&vx<0)) sx+=vx,sy+=vy;
}
auto sqr=[&](const i128& x,const i128& y)
{
return x*x+y*y;
};
sort(p+1,p+cnt+1); i128 ans=sqr(sx,sy);
for (i=1;i<=cnt;++i) sx+=p[i].x,sy+=p[i].y,ans=max(ans,sqr(sx,sy));
return print(ans),0;
}
Postscript
明天开始一轮集训了,有点紧张和期待的说