Codeforces #686 C - F 题解
前言
手慢了,再给我10min就能 ak 了/dk
C - Sequence Transformation
题目大意
给出一个数列,选择一个数使得这个数将这个数列划分成的段数最小。
解题思路
直接记录一个数上一次的出现位置,如果上个位置 \(x\) 与当前位置 \(y\) 的距离 \(y-x>1\) ,那么这个数划分成的段数就加一。
Code
const int N=2e5+5;
int t,n,vis[N],sum[N],ok[N];
signed main()
{
t=read();
while(t--)
{
n=read();
for(int i=1;i<=n;++i)
vis[i]=0,sum[i]=0,ok[i]=0;
for(int i=1;i<=n;++i)
{
int a=read();
if(vis[a]!=i-1)
sum[a]++;
vis[a]=i;ok[a]=1;
}
for(int i=1;i<=n;++i)
if(vis[i]!=n)
sum[i]++;
int ans=1e18;
for(int i=1;i<=n;++i)
if(ok[i])
ans=min(ans,sum[i]);
printf("%lld\n",ans);
}
return 0;
}
D - Number into Sequence
题目大意
构造一个数列使得这个数列每个数都大于 \(1\) 且乘积为 \(n\),后一个能整除前一个,使得这个数列最长。
解题思路
一个显然的想法是先把 \(n\) 质因数分解。
因为分解的质因数都互质,所以如果要满足后一个能整除前一个,后一个就必须由前一个乘上一个质因子。
但是如果数列中的好几个个数由好几个质因子们的乘积,那么不如把它们拆开成几个 质因子 和 一个质因子们的乘积。
所以就相当于找出一个数量最多的质因子 \(p\) ,假如它的数量是 \(q\) ,那么数列的长度最长长度就是 \(q\),即 \(q-1\) 个 \(p\) 和一个 \(\dfrac{n}{p^{q-1}}\)
Code
const int N=1e6+5;
int t,n,m,k,a[N],b[N];
struct Node
{
int x,y;
bool operator < (const Node &p)const
{
return y>p.y;
}
}c[N];
signed main()
{
t=read();
while(t--)
{
n=k=read();m=0;
for(int i=2;i*i<=n;++i)
{
if(n%i==0) a[++m]=i;
while(n%i==0){n/=i,++b[m];}
}
if(n>1)a[++m]=n,b[m]=1;
for(int i=1;i<=m;++i)
c[i]=(Node){a[i],b[i]};
sort(c+1,c+1+m);
int res=c[1].y;
printf("%lld\n",res);
for(int i=1;i<=c[1].y-1;++i)
printf("%lld ",c[1].x),k/=c[1].x;
printf("%lld\n",k);
for(int i=1;i<=m;++i)
a[i]=0,b[i]=0;
}
return 0;
}
E - Number of Simple Paths
题目大意
给一个环基树,问有多少条长度大于 \(1\) 的简单路径。
解题思路
显然如果两个点在环基树中的同一个树中,那么他们之间只存在 \(1\) 条简单路径。
如果两个点在环基树的不同树中或有一个在环上,那么他们之间存在 \(2\) 条简单路径(从环的一半边走或从环的另一半边走)。
所以可以先拓扑排序找出环,然后统计出环基树中每棵树的大小。
显然一棵树对答案的贡献就是 树的大小 \(\times\) \((\) 每个点与树内点的路径数 \(+\) 每个点与树外点的路径数 \()\)。
设这可数的大小为 \(siz\) 则 \(siz\times ((siz-1) + (n-siz)\times2)\)
Code
const int N=2e5+5;
int t,n,vis[N],on[N],col[N],siz[N],du[N],ans,flag;
struct Edge
{
int v,ne;
}e[N*2];
int head[N],tot;
inline void add(int u,int v);
void dfs(int u,int fa,int c);
queue<int> qu;
signed main()
{
t=read();
while(t--)
{
n=read();ans=0;
for(int i=1;i<=n;++i)
{
int u=read(),v=read();
add(u,v);add(v,u);du[u]++,du[v]++;
}
while(!qu.empty()) qu.pop();
for(int i=1;i<=n;++i)
if(du[i]==1) qu.push(i);
while(!qu.empty())
{
int u=qu.front();
qu.pop();
on[u]=1;
for(int i=head[u];i;i=e[i].ne)
{
int v=e[i].v;
du[v]--;
if(du[v]==1) qu.push(v);
}
}
int m=0;
for(int i=1;i<=n;++i)
if(on[i]==0) dfs(i,0,++m);
ans=0;
for(int i=1;i<=m;++i)
ans+=siz[i]*(siz[i]-1+(n-siz[i])*2);
printf("%lld\n",ans/2);
for(int i=1;i<=n;++i)
{
head[i]=0;du[i]=0;
vis[i]=0;on[i]=0;
col[i]=0;siz[i]=0;
}
tot=0;flag=0;
}
return 0;
}
inline void add(int u,int v)
{
e[++tot]=(Edge){v,head[u]};
head[u]=tot;
}
void dfs(int u,int fa,int c)
{
col[u]=c;
siz[c]++;
for(int i=head[u];i;i=e[i].ne)
{
int v=e[i].v;
if(v==fa||on[v]==0)
continue;
dfs(v,u,c);
}
}
F - Array Partition
题目大意
把一个数列划分成三段,使得 第一段的 \(\max=\) 第二段的 \(\min=\) 第三段的 \(\max\)
解题思路
我们假设第一段和第二段的分解点是 \(x\),第二段和第三段的分解点是 \(y\)。
我们可以枚举第一段和第二段的分界点 \(x\) ,这样就知道了要使第二段的 \(\min\) 和第三段的 \(\max\) 要等于的值。
可以先考虑如何让第二段的 \(\min\) 与 第一段的 \(\max\) 相等。
由于第二段区间 \([x,y]\) 的 \(\min\) 一定是单调不升的,所以可以二分这个位置,这样就能在找到一个区间 \([p,q]\) ,在这个区间选第二段和第三段的分界点,就能保证第二段的 \(\min\) 与 第一段的 \(\max\) 相等。
再考虑如何保证第三段的 \(\min\) 与 第一段的 \(\max\) 相等。
其实刚才求出的区间 \([p,q]\) 的 \(\max\) 也是单调不降的,所以依旧可以二分。
如果能找到,那么直接输出这个方案就行了。
二分的时候要维护区间极值,可以直接套一个 st表 或 线段树。
时间复杂度 \(\text{O}(n\ \log^2n)\)
Code
const int N=2e5+5;
int t,n,a[N],val[N*4],suc[N],flag;
void build(int x,int l,int r);
int query(int x,int l,int r,int L,int R);
inline int binary1(int x,int p);
inline int binary2(int x,int l,int r);
signed main()
{
t=read();
while(t--)
{
n=read();
for(int i=1;i<=n;++i)
a[i]=read();
build(1,1,n);
for(int i=n;i>=1;--i)
suc[i]=max(suc[i+1],a[i]);
int now=0;flag=0;
for(int i=1;i<=n;++i)
{
now=max(now,a[i]);
int pos1=binary1(now,i+1)+1;
int pos2=binary1(now+1,i+1)+2;
if(pos1==-1) continue;
if(pos2==0) pos2=i+2;
if(pos1>n) pos1=n;
if(pos2>n) pos2=n;
int pos3=binary2(now,pos2,pos1);
if(pos3==-1) continue;
if(i<=0||pos3-1-i<=0||n-(pos3-1-i)-i<=0) continue;
if(suc[pos3]!=now||query(1,1,n,i+1,pos3-1)!=now)
continue;
printf("YES\n%lld %lld %lld\n",i,pos3-1-i,n-(pos3-1-i)-i);
flag=1;break;
}
if(flag==0) printf("NO\n");
for(int i=1;i<=n;++i)
suc[i]=0;
}
return 0;
}
void build(int x,int l,int r)
{
if(l==r)
{
val[x]=a[l];
return;
}
build(lc,l,mid);
build(rc,mid+1,r);
val[x]=min(val[lc],val[rc]);
}
int query(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R) return val[x];
int res=1e18;
if(L<=mid) res=min(res,query(lc,l,mid,L,R));
if(mid+1<=R) res=min(res,query(rc,mid+1,r,L,R));
return res;
}
inline int binary1(int x,int p)
{
int l=p,r=n,res=-2;
while(l<=r)
{
if(query(1,1,n,p,mid)>=x)
{
res=mid;
l=mid+1;
}
else r=mid-1;
}
return res;
}
inline int binary2(int x,int l,int r)
{
int res=-1;
while(l<=r)
{
if(suc[mid]<=x)
{
res=mid;
r=mid-1;
}
else l=mid+1;
}
return res;
}