CodeForces 1419 - Codeforces Round #671 (Div. 2)
芜湖,终于把这场补完了,可以写题解了(bushi
好了,现在我可以说我连 D2A 都不会了。如果 A 不法师塔可以 rank 10+ 然后把大号吊着锤来着。这 pretest 该喷,two pretests, hundreds of tests。
把上次的 2099 浪费掉了,奇迹果然辜负了奇迹啊(
接下来要打持久战了,即每次用大小号中较逊的那个打,我乃有对于人类再生之确信(
CF 比赛页面传送门
A - Digit Game
洛谷题目页面传送门 & CF 题目页面传送门
有一个 \(n\) 位数,两个人轮流操作。先手可以标记从左往右未标记的奇位数,后手可以标记未标记的偶位数。当仅剩一位未标记时,若它为奇数则先手胜,否则后手胜。判断若两人绝顶聪明,则谁胜。本题多测。
\(n\in[1,1000],T\in[1,100]\)。
显然,最终剩下来的那个位在谁手上是固定的。于是若那个人所掌控的所有位置中有它想要的奇偶性那么他就赢了,否则就输了。
然鹅现场我把两个人都看是否有奇数了,显然是一奇一偶的。我真是个 sb:
wssbwssbwssbwssb
5448544854485448
#include<bits/stdc++.h>
using namespace std;
int n;
string a;
void mian(){
cin>>n>>a;
if(n&1){
bool hav=false;
for(int i=0;i<n;i+=2)hav|=(a[i]^48)%2==1;
puts(hav?"1":"2");
}
else{
bool hav=false;
for(int i=1;i<n;i+=2)hav|=(a[i]^48)%2==0;
puts(hav?"2":"1");
}
}
int main(){
int testnum=1;
cin>>testnum;
while(testnum--)mian();
return 0;
}
B - Stairs
洛谷题目页面传送门 & CF 题目页面传送门
一个 \(x\) 级楼梯,有 \(x\) 层台阶,第 \(i\) 层高 \(i\)(用 \(i\) 个砖块)。称 \(x\) 级楼梯是好的当且仅当它可以被恰好 \(x\) 个正方形所恰好覆盖。给定 \(n\),求用不超过 \(n\) 个砖块最多可以造多少个不同的好楼梯。本题多测。
\(n\in\!\left[1,10^{18}\right],T\in[1,1000]\)。
随便手玩玩可以发现结论:\(x\) 级楼梯是好的当且仅当 \(x\) 是 \(2\) 的整次幂 \(-1\)。然后就很好做了,直接从小到大枚举好楼梯级数,直到砖块和超过 \(n\) 位置。
由于那些不证明结论的题解被喷了,然后题解区被清空了,这里证明一下该结论。
注意到每列的顶端(共 \(n\) 个)中任意两个都不可能出现在同一个正方形中。于是若想要一个 \(x\) 级楼梯是好的,摆正方形方案一定是每个正方形各覆盖一列的顶端。于是我们从左往右依次尝试放置正方形。显然到某一列的时候,如果现在不填满该列,以后就没机会填了,于是必须要填满。于是选择对应的边长递推下去,发现原命题是成立的。
#include<bits/stdc++.h>
using namespace std;
#define int long long
void mian(){
int n;
cin>>n;
int now=0;
for(int i=1;;i++){
now+=((1ll<<i)-1)*(1ll<<i)/2;
if(now>n)return cout<<i-1<<"\n",void();
}
}
signed main(){
int testnum=1;
cin>>testnum;
while(testnum--)mian();
return 0;
}
C - Killjoy
洛谷题目页面传送门 & CF 题目页面传送门
有 \(n+1\) 个用户(其中有一个 root),每个用户有一个 rating。一开始只有 root 感染了新冠。可以进行若干次比赛(root 不参加),每次比赛用户的 rating 会变化,唯一的要求是 rating 之和不变。初始时和每次比赛后所有患新冠的人都会传染给所有与他同 rating 的人。问至少几场比赛可以感染所有人。本题多测。
\(n\in[2,1000],T\in[1,100]\)。
结论很显然吧……
- 如果一开始就全等于 root 的 rating 答案就是 \(0\);
- 如果一开始至少有一个等于 root,则让其他人都变成 root,一开始等于 root 的无所谓了,这样显然一定可以实现,答案为 \(1\);
- 否则,若所有人 rating 平均数等于 root,则可以一次性全变成 root,否则需要 \(2\) 场。
#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int n,x;
int a[N+1];
void mian(){
cin>>n>>x;
for(int i=1;i<=n;i++)scanf("%d",a+i);
vector<int> v;
for(int i=1;i<=n;i++)if(a[i]!=x)v.push_back(a[i]);
if(v.empty())return puts("0"),void();
if(v.size()<n)return puts("1"),void();
int sum=0;
for(int i=0;i<=n;i++)sum+=a[i];
if(sum==x*n)return puts("1"),void();
puts("2");
}
int main(){
int testnum=1;
cin>>testnum;
while(testnum--)mian();
return 0;
}
D1 / D2 - Sage's Birthday
洛谷题目页面传送门 & CF 题目页面传送门
有 \(n\) 个数 \(a_i\),你需要将它重新排列使得严格小于两侧的数的数量最大。输出最大值以及排列方案。
\(n\in\left[1,10^5\right]\)。
答案显然具有可二分性。对于一个答案,贪心地摆,从小到大从第二个格子隔一个摆,然后从大到小大的配大的看是否可行即可。
由于要排序,所以复杂度下界是线性对数。然后有些排序后线性的做法也没有必要了,反正总复杂度跟我的二分答案是一样的。
#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
const int N=100000;
int n;
int a[N+1];
pair<bool,vector<int> > sol(int x){
bool flg=true;
flg&=a[n]>a[x];
for(int i=1;i<=x;i++)flg&=a[n-i]>a[x-i+1];
if(!flg)return mp(false,vector<int>());
vector<int> res(n);
for(int i=1;i<=x;i++)res[2*(x-i+1)-1]=a[x-i+1],res[2*(x-i+1)-2]=a[n-i];
res[2*x]=a[n];
for(int i=2*x+1;i<n;i++)res[i]=a[x+i-2*x];
return mp(true,res);
}
void mian(){
cin>>n;
for(int i=1;i<=n;i++)scanf("%d",a+i);
sort(a+1,a+n+1);
pair<int,vector<int> > ans;
ans.X=0;
for(int i=1;i<=n;i++)ans.Y.push_back(a[i]);
for(int i=25;~i;i--)if(ans.X+(1<<i)<=n-1>>1){
pair<bool,vector<int> > p=sol(ans.X+(1<<i));
if(p.X)ans=mp(ans.X+(1<<i),p.Y);
}
cout<<ans.X<<"\n";
for(int i=1;i<=n;i++)printf("%d ",ans.Y[i-1]);puts("");
}
int main(){
int testnum=1;
// cin>>testnum;
while(testnum--)mian();
return 0;
}
E - Decryption
AC 700 祭(
给一合数 \(n\),你需要将它的所有 \(>1\) 的因数排成一个环。问最少多少次在两个数中间插入它们的最小公倍数,能使任意两个相邻的数不互质,并给出方案。本题多测。
\(n\in\left[4,10^9\right],\sum d(n)\leq 2\times10^5\)。
显然次数就是互质相邻对的个数。我们要最小化这个个数。
注意到两个数不互质当且仅当它们有共同的质因子。于是我们将 \(n\) 分解质因数。
然后考虑扫一遍所有的质因子,对于每个质因子只看此位置的后缀质因子可能组成的因数(这样可以保证不重不漏地照顾到每个因数)。贪心的想,每一个质因子所对应的因数集合都要是连在一块的,那么剩下来的就是不同质因子段之间的问题。我们想要答案最小化,于是希望尽可能地好好排列每个质因子段使得不同质因子段相邻的地方是有公共质因子的。
实际上这个非常简单。对于非首尾之间的相邻,我们只需要在前面一段的最后放的是这两个质因子的乘积即可。然后对于首尾的相邻,只需要在第一段前面放的是首尾两个质因子的乘积。这样子答案就是 \(0\) 了。
不过这样太理想化了。段数 \(=2\) 的时候需要特殊考虑,因为两端之间相邻了 \(2\) 次。当 \(2\) 个质因子次数都为 \(1\) 的时候,肉眼可见答案最小为 \(1\),只能处理掉 \(1\) 个相邻;否则是可以的,你把次数 \(>1\) 的放前面来,两个相邻处分别是第一个质因子的 \(1,2\) 次方乘以第二个质因子即可。
然后这题就做完了。所以很多时候一些题要你构造啥方案最小化啥,其实都是个幌子,结论往往是答案只有几种取值的。
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
int gcd(int x,int y){return y?gcd(y,x%y):x;}
vector<pair<int,int> > v;
vector<int> now;
void dfs(int x,int num=1){
if(x==v.size())return now.pb(num),void();
for(int i=0;i<=v[x].X;i++,num*=v[x].Y){
dfs(x+1,num);
}
}
void mian(){
int n;
cin>>n;
v.clear();
for(int i=2;i*i<=n;i++)if(n%i==0){
v.pb(mp(0,i));
while(n%i==0)v.back().X++,n/=i;
}
if(n>1)v.pb(mp(1,n));
sort(v.begin(),v.end());
if(v.size()==2&&v[0].X==1&&v[1].X==1)return printf("%d %d %d\n%d\n",v[0].Y,v[1].Y,v[0].Y*v[1].Y,1),void();
for(int i=0;i<v.size();i++){
now.clear();
dfs(i+1);
vector<int> vv;
for(int j=0;j<now.size();j++)for(int k=1,o=v[i].Y;k<=v[i].X;k++,o*=v[i].Y)vv.pb(now[j]*o);
now=vv;
for(int j=0;j<now.size();j++){
if(i+1<v.size()&&now[j]==v[i].Y*v[i+1].Y)swap(now[j],now.back());
if(i==0){
if(v.size()==2&&now[j]==v[i].Y*v[1].Y*v[1].Y)swap(now[j],now[0]);
if(v.size()>2&&now[j]==v[i].Y*v.back().Y)swap(now[j],now[0]);
}
}
for(int j=0;j<now.size();j++)if(now[j])printf("%d ",now[j]);
}
puts("\n0");
}
int main(){
int testnum=1;
cin>>testnum;
while(testnum--)mian();
return 0;
}
F - Rain of Fire
这分明是个中等题嘛……不知道现场为啥没啥人过,导致我直接放弃去逛 room 了,也不知道是如何评到 2800 的。
洛谷题目页面传送门 & CF 题目页面传送门
给定平面上 \(n\) 个点 \((x_i,y_i)\),你可以任选出发点,每次可以走到本行或本列距离不超过 \(t\) 的点,要求最终每个点至少走一遍。你可以随意在空白位置放一个附加点(这个点也要走),或者不放,求最小 \(t\) 或报告无解。
\(n\in[2,1000],|x_i|,|y_i|\leq 10^9\)。
首先显而易见的,\(t\) 具有可二分性。于是我们二分答案,转化为判定当前 \(t\) 是否可行。
注意到这个可以转化为图论模型:对于每个点,让它和所有本行或本列距离不超过 \(t\) 的点连边,然后可以加点或不加点使得图连通就 ok。
注意到这里有一个很显然并且很有用的建图优化,即每个点只需要往上下左右四个方向各连最近的一个符合要求的点即可(没有符合要求的则不连),而不需要真的连「所有」符合要求的点,这样连通性显然不变,并且每个点连边数量是常数,总边数线性。
那么接下来的任务是如何高效的判断加点是否可以。考虑将加的点分为 \(2\) 类。
- 只在横向或纵向上做桥梁。这种点的数量显然是线性的,枚举行 / 列,再枚举本行 / 列所有相邻点对即可;
- 两个方向上都做桥梁的。这个看起来数量不好控制,其实不然。注意到它显然要满足它所在行有点且所在列有点,那么所在行和所在列的数量各是线性的(离散化预处理即可)。直接枚举,是平方的。
然后考虑对于每个可能的加点的位置,如何判是否可行。显然可以先 DFS 连通分解,然后对于每个点就暴力 merge 即可。可撤销并查集的话是平方对数的,那么总复杂度就是平方二次对数,受不了。注意到每个点的 merge 量都是常数,所以完全可以直接暴力 set 或其他什么东西乱搞,看是否能把所有连通分量合并即可,随便怎么搞只要跟 \(n\) 无关就是常数的。
还有一个需要思考的问题,就是第 \(2\) 类点如何快速找到它两个方向上连的点对呢?sb 都会的是 lower_bound
,可是那样会炸。可以 two-pointers 控制到均摊常数。
那么现在一次 check 就控制到平方了。于是总复杂度平方对数。
然后各种笔误 WA 了 2 发,wssb5448。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair
#define X first
#define Y second
#define pb push_back
const int N=1000;
int n;
pair<int,int> a[N+1];
vector<int> nei[N+1];
vector<int> nums_x,nums_y;
void discrete(){
sort(nums_x.begin(),nums_x.end());
nums_x.resize(unique(nums_x.begin(),nums_x.end())-nums_x.begin());
sort(nums_y.begin(),nums_y.end());
nums_y.resize(unique(nums_y.begin(),nums_y.end())-nums_y.begin());
}
int cnt,cid[N+1];
void dfs(int x){
cid[x]=cnt;
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
if(!cid[y])dfs(y);
}
}
vector<pair<int,int> > row[N],col[N];
int now_r[N],now_c[N];
bool chk(int x){
for(int i=1;i<=n;i++)nei[i].clear();
for(int i=0;i<nums_x.size();i++){
for(int j=0;j+1<row[i].size();j++)
if(abs(row[i][j].X-row[i][j+1].X)<=x)
nei[row[i][j].Y].pb(row[i][j+1].Y),nei[row[i][j+1].Y].pb(row[i][j].Y);
}
for(int i=0;i<nums_y.size();i++){
for(int j=0;j+1<col[i].size();j++)
if(abs(col[i][j].X-col[i][j+1].X)<=x)
nei[col[i][j].Y].pb(col[i][j+1].Y),nei[col[i][j+1].Y].pb(col[i][j].Y);
}
cnt=0,memset(cid,0,sizeof(cid));
for(int i=1;i<=n;i++)if(!cid[i])cnt++,dfs(i);
if(cnt==1)return true;
if(cnt<=2){
for(int i=0;i<nums_x.size();i++){
for(int j=0;j+1<row[i].size();j++)
if(cid[row[i][j].Y]!=cid[row[i][j+1].Y]&&abs(row[i][j].X-row[i][j+1].X)+1>>1<=x)return true;
}
for(int i=0;i<nums_y.size();i++){
for(int j=0;j+1<col[i].size();j++)
if(cid[col[i][j].Y]!=cid[col[i][j+1].Y]&&abs(col[i][j].X-col[i][j+1].X)+1>>1<=x)return true;
}
}
memset(now_r,0,sizeof(now_r)),memset(now_c,0,sizeof(now_c));
if(cnt<=4)for(int i=0;i<nums_x.size();i++)for(int j=0;j<nums_y.size();j++){
int xx=nums_x[i],yy=nums_y[j];
while(now_r[i]<row[i].size()&&row[i][now_r[i]].X<yy)now_r[i]++;
while(now_c[j]<col[j].size()&&col[j][now_c[j]].X<xx)now_c[j]++;
if(now_r[i]<row[i].size()&&row[i][now_r[i]].X==yy)continue;
set<int> st;
if(now_r[i]&&abs(row[i][now_r[i]-1].X-yy)<=x)st.insert(cid[row[i][now_r[i]-1].Y]);
if(now_r[i]<row[i].size()&&abs(row[i][now_r[i]].X-yy)<=x)st.insert(cid[row[i][now_r[i]].Y]);
if(now_c[j]&&abs(col[j][now_c[j]-1].X-xx)<=x)st.insert(cid[col[j][now_c[j]-1].Y]);
if(now_c[j]<col[j].size()&&abs(col[j][now_c[j]].X-xx)<=x)st.insert(cid[col[j][now_c[j]].Y]);
if(st.size()==cnt&&*st.begin()==1&&*--st.end()==cnt)return true;
}
return false;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i].X>>a[i].Y,nums_x.pb(a[i].X),nums_y.pb(a[i].Y);
discrete();
for(int i=1;i<=n;i++){
row[lower_bound(nums_x.begin(),nums_x.end(),a[i].X)-nums_x.begin()].pb(mp(a[i].Y,i));
col[lower_bound(nums_y.begin(),nums_y.end(),a[i].Y)-nums_y.begin()].pb(mp(a[i].X,i));
}
for(int i=0;i<nums_x.size();i++)sort(row[i].begin(),row[i].end());
for(int i=0;i<nums_y.size();i++)sort(col[i].begin(),col[i].end());
int ans=4e9;
for(int i=32;~i;i--)if(ans-(1ll<<i)>=0&&chk(ans-(1ll<<i)))ans-=1ll<<i;
cout<<(ans==4e9?-1:ans);
return 0;
}