Codeforces Round 866 (Div. 2)
Preface
不知道为什么现在对不想打游戏刷B站什么的一点兴趣都没有,打开HBR就只想清下体力下线去写题
但学校的DS专题能抢一血的题都被抢的七七八八了,剩下几个难得要死都是些我OI时期都不会的东西(吉司机线段树、划分树之类的)
然后突然想起来昨天这场CF还没写博客,E明天再抽个时间想一下然后写吧
这场本来比赛时让ztc给我截个CDE的图片然后我先从C开始看,如果感觉有思路就用大号打Div1,不然就用小号打Div2
然后C时一眼丁真了,但是D刚开始没注意到重要性质没太多想法,E一眼也没啥想法就跑回去打Div2了
总结一下还是太菜了,这样下去什么时候能上2100啊,得上点强度了的说
A. Yura's New Name
傻逼题,从左到右考虑每个_
,先保证它们的左边都要有^
,然后考虑对结尾的_
的右边进行补全即可
注意特判\(n\)较小的情况
#include<cstdio>
#include<iostream>
#include<string>
#define RI register int
#define CI const int&
using namespace std;
int T,n; string s,t;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (cin>>T;T;--T)
{
RI i; cin>>s; t="";
if (s.size()==1&&s[0]=='_') { puts("2"); continue; }
if (s.size()==1&&s[0]=='^') { puts("1"); continue; }
if (s.size()==2)
{
if (s[0]=='_'&&s[1]=='_') { puts("3"); continue; }
if (s[0]=='^'&&s[1]=='^') { puts("0"); continue; }
puts("1"); continue;
}
for (i=0;i<s.size();++i)
if (s[i]=='_') { if (i==0||s[i-1]=='_') t=t+"^"+s[i]; else t+=s[i]; } else t+=s[i];
if (t[t.size()-1]=='_') t+="_"; cout<<t.size()-s.size()<<endl;
}
return 0;
}
B. JoJo's Incredible Adventures
丁真题,很显然我们如果设串中最长的连续的\(1\)的长度为\(m\),则现在就要考虑对\(m+1\)进行划分,使得\(x+y=m+1\)时\(xy\)的值最大
显然根据均值不等式令\(x=\lfloor\frac{m+1}{2}\rfloor,y=x=m+1-\lfloor\frac{m+1}{2}\rfloor\)即可,注意到我们在找最长的\(m\)时时可以从最后接到前面去的
但注意一个Corner Case
,当\(m=n\),即所有数都是\(1\)时要特判输出\(n\times n\)
#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n; char s[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; int lst=0,mx=0; for (scanf("%s",s+1),n=strlen(s+1),i=1;i<=2*n-1;++i)
if (s[(i-1)%n+1]=='0') mx=max(mx,i-lst-1),lst=i; mx=max(mx,2*n-1-lst); ++mx;
printf("%lld\n",mx>n?1LL*n*n:1LL*(mx/2)*(mx-mx/2));
}
return 0;
}
C. Constructive Problem
刚开始心态太浮躁都没细心想,其实很简单的一个题
首先我们求出原序列的\(\operatorname{MEX}(a)=t\),显然由于\(\operatorname{MEX}\)的性质若\(t=n\)则必然无解
否则我们可以把修改的\(k\)定为\(t\),然后考虑在修改后\(a\)中不能出现\(t+1\)
因此我们找出原来的序列中包含所有\(t+1\)的最小区间,然后把这段区间赋值成\(t\)后再检验一遍即可
#include<cstdio>
#include<iostream>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,a[N],L,R,mex; set <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),s.clear(),i=1;i<=n;++i)
scanf("%d",&a[i]),s.insert(a[i]);
for (mex=0;s.count(mex);++mex);
if (mex==n) { puts("No"); continue; }
for (L=n+1,R=0,i=1;i<=n;++i)
if (a[i]==mex+1) L=min(L,i),R=max(R,i);
if (L>R) { puts("Yes"); continue; }
for (s.clear(),i=L;i<=R;++i) a[i]=mex;
for (i=1;i<=n;++i) s.insert(a[i]);
int tmp=mex; for (mex=0;s.count(mex);++mex);
if (mex==tmp+1) puts("Yes"); else puts("No");
}
return 0;
}
D. The Butcher
有点意思的题,感觉是最近做到的最有意思的思博题了
首先我们考虑对于原来最大的矩形,第一刀切下去时一定会有某一维中保留下了原来的最大的那条边
换句话说,我们记\(mxh=\max a_i,mxw=\max b_i\),则显然最后答案只可能是\(h=mxh\)和\(w=mxw\)两种情况
同时我们发现由于所有矩形的面积之和是可以求出来的,因此我们也很容易推出原来的矩形的另一维是多少
那么现在问题就是给定了\(h,w\)后怎么检验它是否合法,显然根据前面的思路我们发现如果在剩下的碎片中存在\(\max a_i=h\)的,则这些碎片一定是在保留\(h\)这一维的基础上在另一维上切割得出来的
当然另一位也同理,因此就很好模拟这个切割过程了,总复杂度\(O(n\log n)\)
#include<cstdio>
#include<iostream>
#include<vector>
#include<set>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,M=1e6+5;
int t,n,a[N],b[N],ans[5][2],cnt,mxa,mxb;
bool vis[N]; long long area; vector <int> A[M],B[M]; multiset <int> LA;
inline bool check(int h,int w)
{
RI i; for (LA.clear(),i=1;i<=n;++i) A[a[i]].clear(),B[b[i]].clear(),vis[i]=0;
for (i=1;i<=n;++i) A[a[i]].push_back(i),B[b[i]].push_back(i),LA.insert(a[i]);
while (h>0&&w>0)
{
bool is_remove=0;
if (h==*(--LA.end()))
{
for (int id:A[h]) if (!vis[id]&&w>=b[id])
vis[id]=1,w-=b[id],is_remove=1,LA.erase(LA.find(a[id]));
} else
{
for (int id:B[w]) if (!vis[id]&&h>=a[id])
vis[id]=1,h-=a[id],is_remove=1,LA.erase(LA.find(a[id]));
}
if (!is_remove) return 0;
}
return 1;
}
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%lld",&t);t;--t)
{
RI i; for (scanf("%lld",&n),area=cnt=mxa=mxb=0,i=1;i<=n;++i)
scanf("%lld%lld",&a[i],&b[i]),mxa=max(mxa,a[i]),mxb=max(mxb,b[i]),area+=a[i]*b[i];
if (area%mxa==0&&check(mxa,area/mxa)) ++cnt,ans[cnt][0]=mxa,ans[cnt][1]=area/mxa;
if (area%mxb==0&&mxa!=area/mxb&&check(area/mxb,mxb)) ++cnt,ans[cnt][0]=area/mxb,ans[cnt][1]=mxb;
for (printf("%lld\n",cnt),i=1;i<=cnt;++i) printf("%lld %lld\n",ans[i][0],ans[i][1]);
}
return 0;
}
E. The Fox and the Complete Tree Traversal
很有趣的思维题,可惜比赛的时候没怎么细想的说,赛后也是手玩了下大概感觉有这个结论,但也不是很确定的说
后面看了眼题解确实是这样,那感觉其实还是挺丁真的
先直接上结论,有解的充要条件为,整棵树在去掉所有叶子节点后为一条链
首先考虑充分性,考虑从树的一个直径端点开始将树分层,首先先一次跳两步,从某层的点转移到另一层的点时优先遍历叶子节点,最后访问链上的节点
然后往回跳的时候则恰好想法,先遍历链上的再遍历叶子即可,当然实际实现的时候不需要真的分层来统计,直接DFS即可
然后考虑必要性,考虑如果删去所有叶子后得到的不是一条链,则说明其实必然有一个点的度数大于等于\(3\)
手玩一下发现在这个点处一定无法使得三个子树内的点形成回路,因此必要性得证
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=200005;
typedef pair <int,int> pi;
int n,m,x,y,dis[N],ans[N],cnt; vector <int> g[N]; queue <int> q;
inline int BFS(void)
{
dis[1]=1; q.push(1); while (!q.empty())
{
int now=q.front(); q.pop();
for (int to:g[now]) if (!dis[to])
dis[to]=dis[now]+1,q.push(to);
}
return max_element(dis+1,dis+n+1)-dis;
}
inline void DFS(CI now,CI fa=0)
{
for (int u:g[now]) if (u!=fa&&g[u].size()>1)
{
for (int v:g[u]) if (v!=now&&g[v].size()==1) ans[++cnt]=v;
for (int v:g[u]) if (v!=now&&g[v].size()>1) ans[++cnt]=v,DFS(v,u);
ans[++cnt]=u;
}
for (int u:g[now]) if (u!=fa&&g[u].size()==1) ans[++cnt]=u;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (scanf("%d",&n),i=1;i<n;++i)
scanf("%d%d",&x,&y),g[x].push_back(y),g[y].push_back(x);
for (i=1;i<=n;++i) if (g[i].size()!=1)
{
int cur=0; for (int to:g[i]) if (g[to].size()>1) ++cur;
if (cur>2) return puts("No"),0;
}
int st=BFS(); ans[cnt=1]=st; DFS(st);
for (puts("Yes"),i=1;i<=cnt;++i) printf("%d ",ans[i]);
return 0;
}
Postscript
最近的CF可能都要咕咕咕了,因为学校的专题练习题量还是挺大的,而且也快期中考了,需要小小地复习一下的说