CF #673 div2 赛后总结
前言
完成成就:在学校熬夜熬到1点
第一次CF打比赛就炸成这个样子
A
题目翻译:
一个长度为\(n\)的数组,每次选择\(i,j(1≤i,j≤n,i≠j)\),然后执行执行一个操作:\(a[j]=a[i]+a[j]\),并且要求操作完后\(a[j]≤k\),问最多进行多少次操作。
这不直接贪心搞?
很明显每次拿每一个去跟最小的数字copy一下即可,因为用最小的数字增长的最慢啊。
时间复杂度:\(O(n)\)
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1100
using namespace std;
int a[N];
int n,K;
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&K);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
sort(a+1,a+n+1);
int ans=0;
for(int i=2;i<=n;i++)ans+=(K-a[i])/a[1];
printf("%d\n",ans);
}
return 0;
}
因为懒得打找最小直接排序了
B
题目翻译:
有一个对于数组的估值函数\(f\),\(f(a)\)是满足\(i≠j,a[i]+a[j]=T\)的二元组\((i,j)\)的数量,然后让你把\(a\)数组分成\(b,c\)两个数组,同时要求\(f(b)+f(c)\)最小。
不难发现,对于一个数字\(x\),\(T-x\)是固定的,且对于\(T-x\)而言,\(x\)也是固定的,所以只需要把\(x\)和\(T-x\)分到两个不同的数组即可。
但是需要注意的是:\(x=T-x\)的情况时,需要均匀的分配。
时间复杂度:\(O(nlogn)\)
#include<cstdio>
#include<cstring>
#include<map>
#define N 110000
using namespace std;
map<int,int> fuck;//直接用map算了
int a[N],n,m,ans[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
fuck.clear();
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
int x=a[i];
if(fuck.count(m-x)==1)ans[i]=fuck[m-x]^1;
else ans[i]=0;
fuck[x]=ans[i];
}
for(int i=1;i<=n;i++)printf("%d ",ans[i]);
printf("\n");
}
return 0;
}
C
题目翻译:
对于一个长度为\(n\)的数组,你需要对于\([1,n]\)中每个数字都找到其对应的神奇数:
(我们设现在正在进行寻找的数字是\(k\))对于每一段长度为\(k\)的连续子段,如果这些子段中的最小值都是同一个数字,那么这个数字就是数字\(k\)的神奇数,简称\(k-\)神奇数。
不难发现,\(k\)越大,答案只会越小,满足单调性,又发现\(a\)数组的数字都在\([1,n]\)范围内,所以我们针对每一个数字,算出当子段长度大到什么数字时,答案小于等于这个数字,然后用个单调栈维护即可。
时间复杂度:\(O(n)\)
#include<cstdio>
#include<cstring>
#define N 310000
using namespace std;
int a[N],n;
int last[N],minn[N];
int sta[N],top;
inline int mymax(int x,int y){return x>y?x:y;}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)minn[i]=-1,last[i]=0;
for(int i=1;i<=n;i++)
{
int x;scanf("%d",&x);
int val=i-last[x];
minn[x]=mymax(minn[x],val);
last[x]=i;
}
for(int i=1;i<=n;i++)
{
if(last[i])minn[i]=mymax(n-last[i]+1,minn[i]);
}
top=0;
for(int i=n;i>=1;i--)
{
if(minn[i]==-1)continue;
int val=minn[i];
while(top && minn[sta[top]]>=val)top--;
sta[++top]=i;
}
int ll=0;
for(int i=1;i<=n;i++)
{
while(ll<top && minn[sta[ll+1]]<=i)ll++;
if(!ll)printf("-1 ");
else printf("%d ",sta[ll]);
}
printf("\n");
}
return 0;
}
D
题目翻译:
对于一个数组\(a\)(里面的数字都是正数)。
你可以选择\(i,j,x(1≤i,j≤n,0≤x≤10^9)\)并进行一次操作(其实\(x\)小于等于\(10^9\)没用的,反正数组总和也才这么大):
\(a_i-=i*x,a_j+=i*x\)
并且要求每次操作完后的数组中的数字都是非负的,问要多少步才能让所有数字相同,并输出操作次数和操作序列,请注意,操作次数只要在\([0,3n]\)之间即可。
不难发现,\(1\)号节点可以自由的让别人加数字,所以我们不妨考虑把其他的点多余平均数的部分汇聚到\(1\)点。然后我WA了。
但是赛后我通过膜拜奆佬,发现了真正的做法:
也是汇聚到\(1\)点,设平均数为\(ova\),如果\(ova<a[i]<i\),上述做法就会出问题,但是如果我们如果强行凑到\(i\),有可能会面临\(1\)号节点的数字不够的问题。
因此我们需要把所有的数字汇聚到\(1\)点,对于第\(i\)号节点\((i≥2)\),如果\(a[i]\mod i≠0\),那么我们只需要直接把缺的部分补满然后全部扔到\(1\)i即可,因为缺的数字最多是\(i-1\)个,同时因为原始数字的数字都是正数,所以\(1\)在拿走了\([1,i-1]\)中所有数字之后,肯定是大于等于\(i-1\)个数字的,所以肯定可以拿走\(i\)的所有数字。
我们发现,上述的操作方案最多会有\(3(n-1)\)个操作。
#include<cstdio>
#include<cstring>
#define N 15000
#define NN 500005
using namespace std;
int a[N],n,T;
struct ANSWER
{
int i,j,t;
}ans[NN];int top;
int main()
{
scanf("%d",&T);
while(T--)
{
top=0;memset(ans,0,sizeof(ans));
scanf("%d",&n);
int sum=0;
for(int i=1;i<=n;i++){scanf("%d",&a[i]);sum+=a[i];}
if(sum%n!=0)
{
printf("-1\n");
continue;
}
sum/=n;
for(int i=2;i<=n;i++)
{
int val=i-a[i]%i;
if(val!=i)
{
++top;
ans[top].i=1;ans[top].j=i;ans[top].t=val;
a[i]+=val;
}
++top;
ans[top].i=i;ans[top].j=1;ans[top].t=a[i]/i;
}
for(int i=2;i<=n;i++)
{
top++;
ans[top].i=1;ans[top].j=i;ans[top].t=sum;
printf("%d\n",top);
for(int i=1;i<=top;i++)printf("%d %d %d\n",ans[i].i,ans[i].j,ans[i].t);
}
return 0;
}
E
题目翻译:
给你个数组\(a\),要求你找到一个\(x\),使得操作后得到的数组\(b\)的逆序对数最少。
操作法则为对于\(a\)中每个数字与\(x\)异或,得到\(b\)。
思路:
我们针对最高位先进行分类讨论。
如果都是\(0\)的话,那么我们不难发现这些要判断逆序对数只能靠下一位,不管他。
\(1\)也是。
但是\(0,1\)交替呢?我们先讨论这一位,我们统计一下这一位是\(0\)时或者是\(1\)时可以贡献的逆序对数。
然后讨论这一位是\(0/1\)是对于下一位的影响,我们设现在的数组是\(a\),最高位是\(1\)的数字的数组是\(a1\),是\(0\)的数组是\(a0\),我们发现,不管这一位是\(0\)还是\(1\),在下一位中\(a0,a1\)都是独立的产生贡献的(因为交叉产生贡献在这一位就处理了),所以我们只要在一位一位处理并不断分裂即可。
至于例子,拿样例手操一下就知道了。
考试我没有开long long!!!
#include<cstdio>
#include<cstring>
#define N 610000
using namespace std;
typedef long long LL;
int aa[2][N],n,shit,mid;
LL to1,to2;
inline void chuli(int *a,int top,int *b)
{
int zt=0,ot=0;
for(int i=top;i>=1;i--)
{
if(a[i]&shit)ot++;
else zt++;
}
int zc=0,oc=0;
for(int i=top;i>=1;i--)
{
if(a[i]&shit)
{
oc++;b[top-oc+1]=a[i];
to2+=zc;
}
else
{
zc++;b[zt-zc+1]=a[i];
to1+=oc;
}
}
mid=zt;
}
int ll[2][N],rr[2][N],top[2];
int main()
{
long long ans2=0,ans1=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&aa[0][i]);
top[0]=1;ll[0][1]=1;rr[0][1]=n;
int pre=1,now=0;
for(int i=30;i>=1;i--)
{
now^=1;pre^=1;
shit=(1<<(i-1));
top[now]=0;
to1=to2=0;
for(int i=1;i<=top[pre];i++)
{
chuli(aa[pre]+ll[pre][i]-1,rr[pre][i]-ll[pre][i]+1,aa[now]+ll[pre][i]-1);
if(mid>0)
{
int x=++top[now];
ll[now][x]=ll[pre][i];
rr[now][x]=ll[pre][i]+mid-1;
}
if(mid!=rr[pre][i]-ll[pre][i]+1)
{
int x=++top[now];
ll[now][x]=ll[pre][i]+mid;
rr[now][x]=rr[pre][i];
}
}
if(to1>=to2)ans2+=to2;
else ans2+=to1,ans1^=shit;
}
printf("%lld %lld\n",ans2,ans1);
return 0;
}
F
题目翻译:
一开始有\(n\)个点,\(m\)条边,\(q\)个询问,然后接下来有\(q\)个询问(有两种):
- 对于\(1\)号询问,是询问\(x\)所能到达的点(包括自己)的\(p\)的最大值,然后输出,并且把\(p_{max}\)所在的点的\(p\)改为\(0\)。(注意,初始的每个点的\(p\)并不一样)。
- 删除一条边。
思路:
需要注意的是,基本上很多图上删边的题目都是离线倒着处理,但是这道题目特别狗蛋的是前面的询问对后面有后效性。
我们从后往前扫每个删边操作,然后得到每一次删边之前的联通信息,但是如何维护就成了重中之重,同时还要支持查询和修改。
由于每次最多合并两个联通块,所以我们可以开个新点连接这两个联通块,且以后的管理节点在连接这个新的联通块时也是连接在这个点上,这样不难发现这是一个树的结构,而且还是一个二叉树。
至于快速找到一个点的最高层的管理节点,并查集!!!
而且这个树还有一个很优秀的性质:一个管理节点的子树就是其在分裂前的联通块。
当然,这个树还有许多很好的性质,但是此题没用。
看到第二点,我们很容易想到用\(dfs\)序搞,线段树维护,然后对于每个询问只要在从后往前搞的时候顺便记录一下这个点联通块的最高层管理节点是哪个即可。
#include<cstdio>
#include<cstring>
#define N 210000
#define NN 410000
#define M 310000
using namespace std;
inline int mymax(int x,int y){return x>y?x:y;}
int n,m,q;
struct TREE
{
int l,r,c;
}tr[NN];int len,last[N],p[N],be[N],id[N],valbe[N];
void bt(int l,int r)
{
int now=++len;
if(l==r)tr[now].c=p[be[l]];
else
{
int mid=(l+r)>>1;
tr[now].l=len+1;bt(l,mid);
tr[now].r=len+1;bt(mid+1,r);
tr[now].c=mymax(tr[tr[now].l].c,tr[tr[now].r].c);
}
}
void change(int now,int l,int r,int id)
{
if(l==r){tr[now].c=0;return ;}
int mid=(l+r)>>1;
if(id<=mid)change(tr[now].l,l,mid,id);
else change(tr[now].r,mid+1,r,id);
tr[now].c=mymax(tr[tr[now].l].c,tr[tr[now].r].c);
}
int findans(int now,int l,int r,int ll,int rr)
{
if(l==ll && r==rr)return tr[now].c;
int mid=(l+r)>>1,lc=tr[now].l,rc=tr[now].r;
if(rr<=mid)return findans(lc,l,mid,ll,rr);
else if(mid<ll)return findans(rc,mid+1,r,ll,rr);
else return mymax(findans(lc,l,mid,ll,mid),findans(rc,mid+1,r,mid+1,rr));
}
int fa[NN];
int findfa(int x)
{
if(fa[x]!=x)fa[x]=findfa(fa[x]);
return fa[x];
}
struct edge
{
int x,y;bool c;
}a[M];
int son[NN][2],cnt;
inline int mer(int x,int y)
{
int tx=findfa(x),ty=findfa(y);
if(tx!=ty)
{
cnt++;fa[tx]=fa[ty]=fa[n+cnt]=n+cnt;
son[n+cnt][0]=tx;son[n+cnt][1]=ty;
}
}
int ll[NN],rr[NN],top;
inline void dfs(int x)
{
ll[x]=top+1;
if(x<=n)id[x]=++top,be[top]=x;
else
{
dfs(son[x][0]);
dfs(son[x][1]);
}
rr[x]=top;
}
struct Query
{
int type,x;
}qu[510000];
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++){scanf("%d",&p[i]);fa[i]=i;valbe[p[i]]=i;}
for(int i=1;i<=m;i++)scanf("%d%d",&a[i].x,&a[i].y);
for(int i=1;i<=q;i++)
{
scanf("%d%d",&qu[i].type,&qu[i].x);
if(qu[i].type==2)a[qu[i].x].c=1;
}
for(int i=1;i<=m;i++)
{
if(!a[i].c)mer(a[i].x,a[i].y);
}
for(int i=q;i>=1;i--)
{
if(qu[i].type==2)mer(a[qu[i].x].x,a[qu[i].x].y),qu[i].type=-1;
else qu[i].type=findfa(qu[i].x);
}
for(int i=n+cnt;i>=1;i--)
{
if(findfa(i)==i)dfs(i);
}
bt(1,n);
for(int i=1;i<=q;i++)
{
if(qu[i].type!=-1)
{
int x=qu[i].type;
int y=findans(1,1,n,ll[x],rr[x]);
if(y)change(1,1,n,id[valbe[y]]);
printf("%d\n",y);
}
}
return 0;
}