CodeTON Round 4 (Div. 1 + Div. 2, Rated, Prizes!)

Preface

唉难得熬夜打一把还天天掉分,苦路西

这把状态奇差,CD两个傻逼题都写的很慢,然后做E的时间太少最后又是经典比赛结束才调出来

虽然很想骂傻逼室友打游戏鬼叫的超级响导致我注意力很难集中,不过终究还是自己抗干扰水平不够,不能怨天尤人


A. Beautiful Sequence

傻逼题,显然若一个位置\(i\)满足\(i\ge a_i\)则通过删除\(i\)之前的某些数一定可以满足题意,直接判断即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,n,a[N];
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]);
		bool flag=0; for (i=1;i<=n;++i) if (i>=a[i]) flag=1;
		puts(flag?"YES":"NO");
	}
}

B. Candies

首先一个很naive的结论,我们只能生成奇数,因此先特判掉偶数的情况

然后我们手玩一下画一下前几次操作的图出来,就一眼能找到规律,所有数之间形成了一棵二叉树的结果,并且所有数从大到小在树上依次分布

那么这个题就很好处理了,我们把每个奇数的排名(就是第几小的奇数)搞出来做一个类似二进制分解的东西即可

#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)
	{
		if (scanf("%d",&n),!(n&1)) { puts("-1"); continue; }
		if (n==1) { puts("0"); continue; }
		RI i,j; for (n=(n-1)/2,i=30;~i;--i) if (n&(1<<i)) break;
		for (printf("%d\n",i+1);~i;--i) printf("%c%c",n&(1<<i)?'2':'1'," \n"[i==0]);
	}
	return 0;
}

C. Make It Permutation

一个朴素的想法,先把所有重复的元素删成一个,然后我们可以直接枚举最后排列的长度,就很容易写出一个式子

其中要插入的次数就是排列的长度\(x\)减去小于等于\(x\)的元素个数,要删除的次数就是大于\(x\)的元素个数

但是有一个问题是最终排列的长度可能是\(10^9\)级别的,不能直接枚举

不过我们对上面的式子稍加观察,我们会发现这个式子要取得最小值只能在“小于等于\(x\)的元素个数”这个值发生改变的时候取

换而言之我们只要枚举所有出现过的数作为最后排列的长度即可,枚举的复杂度就降到\(O(n)\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,c,d,a[N]; long long ans;
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; for (scanf("%lld%lld%lld",&n,&c,&d),i=1;i<=n;++i) scanf("%lld",&a[i]);
		sort(a+1,a+n+1); int m=unique(a+1,a+n+1)-a-1;
		for (ans=1e18,i=1;i<=m;++i)
		ans=min(ans,1LL*(a[i]-i)*d+1LL*(m-i)*c);
		printf("%lld\n",min(1LL*n*c+d,ans+1LL*(n-m)*c));
	}
	return 0;
}

D. Climbing the Tree

感觉没啥好说的,就一大力模拟题

首先对于每一个\(1\)操作,我们都可以计算出它对应的一个\(h\)的区间,然后我们把这个区间和之前所有\(1\)操作的区间取交集就是现在\(h\)可能的取值区间\([L,R]\)

具体的计算也很简单,考虑下界就是让蜗牛第\(n-1\)天恰好爬不到,上界就是第\(n\)天刚好爬到,但要注意一些边界情况

然后考虑处理询问,很明显蜗牛要爬的天数关于树高是单调不减的,因此如果对于\(h=L\)\(h=R\)的树高都需要爬相同的天数那\([L,R]\)之间的任意一天都需要爬相同的天数

然后就模拟一下就完了,只能说这场的CD是真的简单(1300、1700),但我感觉对比周三的那场(1900,2100)我好像写的更久

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,INF=1e18;
int t,q,tp,a,b,n;
inline int calc(CI h)
{
	if (a>=h) return 1; return (h-a+(a-b-1))/(a-b)+1;
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; int L=1,R=INF;
		for (scanf("%lld",&q),i=1;i<=q;++i)
		{
			if (scanf("%lld%lld%lld",&tp,&a,&b),tp==2)
			{
				if (R==INF) { printf("-1 "); continue; }
				int c1=calc(L),c2=calc(R);
				if (c1!=c2) printf("-1 "); else printf("%lld ",c1);
			} else
			{
				scanf("%lld",&n); int l=n>1?(a-b)*(n-2)+a+1:1,r=(a-b)*(n-1)+a;
				if (max(l,L)>min(r,R)) printf("0 "); else L=max(l,L),R=min(r,R),printf("1 ");
			}
		}
		putchar('\n');
	}
	return 0;
}

E. Monsters

唉只能说我想题和写题的速度都还是有点慢,每次比赛都有一道题要血战到最后一刻

虽然以前有几次也能堪堪压时过,但分数也是寥寥无几了,这次还比较可惜没调出来(其实就是个傻逼下标写反了)

好了说回题目,这题我本来的思路是按照每条边两端的点权值的差的绝对值从小到大排序

然后每次处理一条边的时候就尝试合并,通过并查集来维护每个连通块的大小,看起来就挺正确

后面写完调试样例的时候感觉不对劲,有些比如联通了权值为\((3,3)\)的边显然不应该先处理,因为它们还有等一些比如\((1,2)\)这样的边处理之后再联通的机会

因此后面改成了按照每条边两端的点的权值的最大值从小到大来排序就很正确了,代码也是十分好写

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,m,a[N],x,y,fa[N],sz[N],vis[N];
struct edge
{
	int v,x,y;
	friend inline bool operator < (const edge& A,const edge& B)
	{
		return A.v<B.v;
	}
}e[N];
inline int getfa(CI x)
{
	return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d%d",&n,&m),i=1;i<=n;++i)
		scanf("%d",&a[i]),fa[i]=i,sz[i]=1,vis[i]=a[i]==0;
		for (i=1;i<=m;++i) scanf("%d%d",&e[i].x,&e[i].y),e[i].v=max(a[e[i].y],a[e[i].x]);
		for (sort(e+1,e+m+1),i=1;i<=m;++i)
		{
			int fx=getfa(e[i].x),fy=getfa(e[i].y);
			if (fx==fy) continue; 
			vis[fx]=((vis[fx]&&sz[fx]>=e[i].v)||(vis[fy]&&sz[fy]>=e[i].v));
			fa[fy]=fx; sz[fx]+=sz[fy];
		}
		puts(sz[getfa(1)]==n&&vis[getfa(1)]?"YES":"NO");
	}
}

后来ztc跟我讲了一个BFS的做法,但是他的做法一些维护细节我也不是很理解,不过后来顺着想想发现了我的另一种实现方法

就是我们从所有\(a_i=0\)的点为源点做BFS,然后每次向外走之后可以把当前走到的所有点合并看作一个大的连通块来看待

然后考虑对于每个连通块我们每次要找到这些点向外的连边可达的节点中,\(a_i\)最小的那个来扩展,如果找得到并且这个值小于等于当前连通块就扩展,否则这个连通块就没用了

我们考虑对每一个连通块用堆维护它出点的\(a_i\),然后考虑在合并的时候用启发式合并合并两个堆,最后复杂度是\(O(n\log^2 n)\)

代码就没写了,口胡一下

Upt:今天看F题Tutorial的时候看了下E题官方的做法就是这个启发式合并堆的,那思路应该没问题的说


F. M-tree

真给这题的神仙思路跪了,完全想不到这么妙的转化的说

首先我们设\(num_i\)表示\(a_j=i\)\(j\)的个数,然后我们考虑一个朴素的处理单次询问的做法,我们二分一个答案\(x\),然后检验答案是否能\(\le x\)

考虑贪心地动态检验,设当前的叶子个数为\(d\),然后我们从大到小检验每个\(i\),如果当前的\(d<num_i\)则显然无解,否则我们可以把多余的叶子节点生长到下一层

直接说可能有点抽象,直接看下面的代码就很好理解(直接copy自官方Tutorial)

bool check(int x)
{
    int d = 1;
    for(int i = x;i >= 1;i--){
        if(d < num[i]) return 0;
        d = (d - num[i]) * m;
    }
    return 1;
}

然后由于若\(d\)在途中变成负数了那么答案最后也一定是负数,因此我们可以修改为:

bool check(int x)
{
    int d = 1;
    for(int i = x;i >= 1;i--){
        d = (d - num[i]) * m;
    }
    return d >= 0;
}

上面的计算过程看起来就很像一个东西,我们展开下就发现最后的\(d=m^x-\sum_{i=1}^x num_i\times m^i\),移项后就是\(m^x\ge \sum_{i=1}^x num_i\times m^i\)

而右边的式子明显就是一个\(m\)进制表示\(num\)数组的东西,因此最后的答案就是\(\lceil\log_m(\sum_{i=1}^n m^{a_i})\rceil\)

因此现在我们需要做的就是对一个\(m\)进制数做位\(+1\),位\(-1\)和查询最高位,借用一个经典的关于线段树维护二进制的同样问题我们有:

  • \(x\)位加\(1\),我们找到\(x\)位向高位走的过程中第一个\(<m-1\)的位\(y\),把\([x,y)\)上的值都置为\(0\),然后把\(y\)上的值加\(1\)即可
  • \(x\)位减\(1\),我们找到\(x\)位向高位走的过程中第一个\(>0\)的位\(y\),把\([x,y)\)上的值都置为\(m-1\),然后把\(y\)上的值减\(1\)即可
  • 查询时注意若最高位为第\(x\)位,若其它所有位上都是\(0\)则答案为\(x\),否则为\(x+1\)

以上的操作显然都可以通过线段树上二分来完成,因此总复杂度\(O(n\log n)\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,m,q,a[N],x,y;
class Segment_Tree
{
	private:
		struct segment_node
		{
			int mi,mx,tag;
			inline segment_node(CI MI=0,CI MX=0,CI TAG=-1)
			{
				mi=MI; mx=MX; tag=TAG;
			}
		}node[N<<4];
		#define MI(x) node[x].mi
		#define MX(x) node[x].mx
		#define T(x) node[x].tag
		#define TN CI now=1,CI l=1,CI r=n+q<<1
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void apply(CI now,CI mv)
		{
			MI(now)=MX(now)=T(now)=mv;
		}
		inline void pushup(CI now)
		{
			MI(now)=min(MI(now<<1),MI(now<<1|1));
			MX(now)=max(MX(now<<1),MX(now<<1|1));
		}
		inline void pushdown(CI now)
		{
			if (~T(now)) apply(now<<1,T(now)),apply(now<<1|1,T(now)),T(now)=-1;
		}
		inline int Lquery(CI pos,TN)
		{
			if (r<pos) return r+1;
			if (pos<=l)
			{
				if (MI(now)==m-1) return r+1;
				if (l==r) return l; int mid=l+r>>1; pushdown(now);
				if (MI(now<<1)<m-1) return Lquery(pos,LS); else return Lquery(pos,RS);
			}
			int mid=l+r>>1; pushdown(now); int ret=mid+1;
			if (pos<=mid) ret=Lquery(pos,LS);
			if (ret==mid+1) ret=Lquery(pos,RS); return ret;
		}
		inline int Rquery(CI pos,TN)
		{
			if (r<pos) return r+1;
			if (pos<=l)
			{
				if (MX(now)==0) return r+1;
				if (l==r) return l; int mid=l+r>>1; pushdown(now);
				if (MX(now<<1)>0) return Rquery(pos,LS); else return Rquery(pos,RS);
			}
			int mid=l+r>>1; pushdown(now); int ret=mid+1;
			if (pos<=mid) ret=Rquery(pos,LS);
			if (ret==mid+1) ret=Rquery(pos,RS); return ret;
		}
		inline void modify(CI beg,CI end,CI mv,TN)
		{
			if (beg<=l&&r<=end) return apply(now,mv); int mid=l+r>>1; pushdown(now);
			if (beg<=mid) modify(beg,end,mv,LS); if (end>mid) modify(beg,end,mv,RS); pushup(now);
		}
		inline void updata(CI pos,CI mv,TN)
		{
			if (l==r) return (void)(MI(now)+=mv,MX(now)+=mv); int mid=l+r>>1; pushdown(now);
			if (pos<=mid) updata(pos,mv,LS); else updata(pos,mv,RS); pushup(now);
		}
		inline int Tquery(TN)
		{
			if (l==r) return l; int mid=l+r>>1; pushdown(now);
			if (MX(now<<1|1)>0) return Tquery(RS); else return Tquery(LS);
		}
		inline int MXquery(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return MX(now); int mid=l+r>>1,ret=-1; pushdown(now);
			if (beg<=mid) ret=max(ret,MXquery(beg,end,LS));
			if (end>mid) ret=max(ret,MXquery(beg,end,RS)); return ret;
		}
	public:
		inline void build(TN)
		{
			node[now]=segment_node(); if (l==r) return;
			int mid=l+r>>1; build(LS); build(RS);
		}
		inline void add(CI x)
		{
			int pos=Lquery(x); if (pos>x) modify(x,pos-1,0); updata(pos,1);
		}
		inline void sub(CI x)
		{
			int pos=Rquery(x); if (pos>x) modify(x,pos-1,m-1); updata(pos,-1);
		}
		inline int get(void)
		{
			int pos=Tquery(); if (MXquery(pos,pos)==1&&(pos==1||MXquery(1,pos-1)==0)) return pos; else return pos+1;
		}
		#undef MI
		#undef MX
		#undef T
		#undef TN
		#undef LS
		#undef RS
}T;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; scanf("%d%d%d",&n,&m,&q); T.build();
		for (i=1;i<=n;++i) scanf("%d",&a[i]),T.add(a[i]);
		for (i=1;i<=q;++i)
		scanf("%d%d",&x,&y),T.sub(a[x]),T.add(a[x]=y),printf("%d ",T.get());
		putchar('\n');
	}
	return 0;
}

Postscript

下场我再犯nt错误直接惩罚自己补两场CF,真不能再掉分了苦路西

posted @ 2023-04-06 10:06  空気力学の詩  阅读(20)  评论(0编辑  收藏  举报