Codeforces Round #823 (Div. 2)

Preface

本来没准备打这场比赛的,因为昨天出去high玩了一整天才发现今天才发现晚上有CF,遂报名rush一发

结果今天状态有点崩,先是B看错题导致浪费时间,然后又是D自己叉自己把原来对的思路扔了

最后一看E真NM可做,结果写了个复杂度错的代码(只能跑随机数据)直接凉凉,12:10分为了不影响室友睡觉直接开溜

但由于本身的Rating不高,因此没有掉分也是万幸


A. Planets

SB题,对于每个机场判断下用那种方法销毁飞机即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,n,c,a[N],num[N],ans;
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,&c),i=1;i<=n;++i)
		scanf("%d",&a[i]),++num[a[i]];
		for (ans=0,i=1;i<=100;++i) ans+=min(num[i],c);
		for (i=1;i<=100;++i) num[i]=0;
		printf("%d\n",ans);
	}
	return 0;
}

B. Meeting on the Line

刚开始看错题以为总时间最短,以为是个SB题

后来发现怎么样例输出怎么有小数,遂仔细读题发现还是个SB题

一眼二分答案\(T\),检验的话考虑\(left=T-t_i\)就是每个人除去准备的时间剩下的最大走路时间

因此对第\(i\)个人来说,\(x_0\)可能的位置只能在\([x_i-left,x_i+left]\)中,把每个人的可能区间并起来看看是否为空集即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
const double EPS=1e-8;
struct itv
{
	double l,r;
}; int t,n,a[N],tim[N];
inline void merge(itv& A,const itv& B)
{
	A=(itv){max(A.l,B.l),min(A.r,B.r)};
}
inline bool check(const double& T)
{
	itv cur=(itv){-1e9,1e9}; for (RI i=1;i<=n;++i)
	{
		double left=T-tim[i]; if (left<0) return 0;
		merge(cur,(itv){1.0*a[i]-left,1.0*a[i]+left});
	}
	return cur.r-cur.l>-EPS;
}
inline double getans(const double& T)
{
	itv cur=(itv){-1e9,1e9}; for (RI i=1;i<=n;++i)
	{
		double left=T-tim[i]; if (left<0) return 0;
		merge(cur,(itv){1.0*a[i]-left,1.0*a[i]+left});
	}
	return cur.l;
}
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]);
		for (i=1;i<=n;++i) scanf("%d",&tim[i]);
		double L=0,R=2e8,mid,ans; while (R-L>EPS)
		if (check(mid=(L+R)/2.0)) ans=mid,R=mid; else L=mid;
		printf("%.8lf\n",getans(ans));
	}
	return 0;
}

C. Minimum Notation

有点技巧的贪心题

首先simple地考虑,肯定应该先把所有的0保留并放到最前面,这样的话我们需要把最后一个0前面出现的所有非零数全部删除

考虑最后处理这些被删除的数,显然把他们从小到大扔在最后面即可

那么在处理0时,若最后一个0后面还有其它字符怎么办呢,显然直接在其中重复找1的过程即可,当1找完后若还剩下就找2,以此类推

写个函数处理非常简便

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
char s[N]; int t,n,ct[10];
inline void solve(CI pos,const char& ch)
{
	if (pos>n||ch>'9') return; RI i; int lst=-1;
	for (i=pos;i<=n;++i) if (s[i]==ch) lst=i;
	if (!~lst) return solve(pos,ch+1);
	for (i=pos;i<=lst;++i) if (s[i]==ch) ++ct[ch-'0']; else ++ct[min(9,s[i]-'0'+1)];
	solve(lst+1,ch+1);
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (i=0;i<10;++i) ct[i]=0;
		scanf("%s",s+1); n=strlen(s+1); solve(1,'0');	
		for (j=0;j<10;++j) for (i=1;i<=ct[j];++i) putchar(j+'0'); putchar('\n');
	}
	return 0;
}

D. Prefixes and Suffixes

妈的本来都做出来了撒泡尿回来脑抽了把写了一半的代码删了,真想抽自己一个大耳光子

首先我们发现对于\(a_i\),它和\(b_{n-i+1}\)相对位置还是一样的,即每次操作这两个数要么一起换要么一起不换

因此这两个数不能出现在同一个串中,但它们的顺序和位置是可以任意换的(手玩一下即可)

因此我们把每个\(a_i\)\(b_{n-i+1}\)看作一个无序对\((x,y),x\le y\),统计每种无序对的个数

考虑构造一种合法方案,不难发现每个\((x,y),x\ne y\)的无序对的个数必须是偶数,然后类似回文地排布构造即可

\(n\)为奇数,则\((x,y),x=y\)的个数等于\(1\)是合法的,否则不合法;若\(n\)为偶数,则\((x,y),x=y\)的个数只能为\(0\)

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,S=26;
int t,n,c[S][S]; char a[N],b[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; scanf("%d%s%s",&n,a+1,b+1); memset(c,0,sizeof(c));
		for (i=1;i<=n;++i)
		{
			int x=a[i]-'a',y=b[n-i+1]-'a'; if (x>y) swap(x,y); ++c[x][y];
		}
		bool flag=1; for (i=0;i<S;++i) for (j=i+1;j<S;++j)
		if (c[i][j]&1) flag=0; if (!flag) { puts("NO"); continue; }
		int sum=0; for (i=0;i<S;++i) sum+=c[i][i]%2;
		puts(sum>n%2?"NO":"YES");
	}
	return 0;
}

E. Maximums and Minimums(自己的假算法)

简单地提一下我比赛时的idea吧,今天有点晚了先懒得看官方sol了

首先这种区间最大最小值的经典套路无非两种,单调栈笛卡尔树,但笛卡尔树有一个显著的问题,在处理了其中一个后不好处理另一个(也许是我理解烂,若有方法实现务必请指教)

因此我们考虑单调栈,找出以每个\(a_i\)为最小值时的最大扩展区间,当时naive地以为区间长度总和是\(O(n)\)级别的,然后T了

其实仔细想想也是,如果放在笛卡尔树上这部分相当于所有子树的size和,显然如果树极不平衡(如退化成链)则总和是\(O(n^2)\)级别的

因此也说明了为什么我们在利用笛卡尔树处理问题时通常要加上启发式合并和DSU on tree的姿势了,不然复杂度是会爆炸的

说回正题,先不考虑复杂度,此时我们应该如何统计答案,由于还要考虑区间最大值,因此我们从\(a_i\)开始,向左右两边统计前缀最大值

考虑在\([L_i,i],[i,R_i]\)\(L_i,R_i\)分别为\(a_i\)的最大扩展区间的左右端点)中各取一个前缀最大值,二者的最大值即为区间的最大值

现在先考虑在\([i,R_i]\)中枚举每一个前缀最大值\(pfx_j\),分情况讨论:

  • \(a_i|pfx_j\),此时在\([L_i,i]\)中合法的\(pfx_k\)为那些小于等于\(pfx_j\)的数量以及\(a_i|pfx_k\)\(pfx_k>pfx_j\)的数量之和
  • \(a_i\nmid pfx_j\),此时在\([L_i,i]\)中合法的\(pfx_k\)为那些\(a_i|pfx_k\)\(pfx_k>pfx_j\)的数量

显然可以开两个树状数组来维护\([L_i,i]\)中的信息,一个维护是\(a_i\)的倍数的一个维护不是\(a_i\)的倍数的

设区间总长度为\(S\),总复杂度为\(O(S\log a_i)\)(当数据随机时\(S=n\log n\)跑得飞快,随便过几十万的数据点)

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=500005;
int t,n,a[N],top,stk[N],L[N],R[N],lim,pfx; long long ans;
#define lowbit(x) (x&-x)
class Tree_Array1
{
	private:
		long long bit[N<<1];
	public:
		inline void add(int x,CI y)
		{
			for (;x<=lim;x+=lowbit(x)) bit[x]+=y;
		}
		inline int get(int x,int ret=0)
		{
			for (;x;x-=lowbit(x)) ret+=bit[x]; return ret;
		}
}T1;
class Tree_Array2
{
	private:
		long long bit[N<<1];
	public:
		inline void add(int x,CI y)
		{
			for (;x;x-=lowbit(x)) bit[x]+=y;
		}
		inline int get(int x,int ret=0)
		{
			for (;x<=lim;x+=lowbit(x)) ret+=bit[x]; return ret;
		}
}T2;
#undef lowbit
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d",&n),lim=ans=0,i=1;i<=n;++i)
		scanf("%d",&a[i]),lim=max(lim,a[i]);
		for (top=0,i=1;i<=n;++i)
		{
			while (top&&a[i]<a[stk[top]]) --top;
			if (!top) L[i]=1; else L[i]=stk[top]+1; stk[++top]=i;
		}
		for (top=0,i=n;i;--i)
		{
			while (top&&a[i]<=a[stk[top]]) --top;
			if (!top) R[i]=n; else R[i]=stk[top]-1; stk[++top]=i;
		}
		for (i=1;i<=n;++i)
		{
			for (pfx=0,j=i;j>=L[i];--j)
			if (pfx=max(pfx,a[j]),T1.add(pfx,1),pfx%a[i]==0) T2.add(pfx,1);
			for (pfx=0,j=i;j<=R[i];++j)
			if (pfx=max(pfx,a[j]),ans+=T2.get(pfx+1),pfx%a[i]==0) ans+=T1.get(pfx);
			for (pfx=0,j=i;j>=L[i];--j)
			if (pfx=max(pfx,a[j]),T1.add(pfx,-1),pfx%a[i]==0) T2.add(pfx,-1);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

E. Maximums and Minimums(正解)

好了现在来补一发官方题解,还是考虑用单调栈来维护极值区间,但在计算答案的地方有优化

\(ll_i,lr_i,gl_i,gr_i\)分别表示\(i\)左边的最近的大于\(a_i\)的数、\(i\)右边的最近的大于\(a_i\)的数、\(i\)左边的最近的小于等于\(a_i\)的数、\(i\)右边的最近的小于\(a_i\)的数

考虑枚举每一个\(a_i\)作为区间的最大值,假设这个区间为\([l,r]\),则\(gl_i<l\le i\le r<gr_i\)

接下来考虑枚举\(d|a_i\),考虑找出\(i\)左右两侧最近的\(j_1,j_2\)满足\(a_{j_1}=a_{j_2}=d\),首先考虑加入\(j_1\)后要多出哪些限制

由于\(d\)应该为区间\([l,r]\)的最小值,因此\(ll_{j_1}<l\le j_1\le r<lr_{j_1}\),而有了上面的两条限制我们就可以直接算出\([l,r]\)的可取数目了

对于\(j_2\)的情况类似,这里不再赘述,因为要避免\(j_1,j_2\)的选法重复,需要保证对于\(j_2\)来说的\(l\),满足\(j_1<l\)

总复杂度\(O(A\log A+T\times nD)\),其中\(D\)表示所有数的因子数目之和

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=1000005;
int t,n,a[N],top,stk[N],ll[N],lr[N],gl[N],gr[N],idx[N];
vector <int> frac[N],pos[N]; long long ans;
inline void init(CI n)
{
	for (RI i=1;i<=n;++i) for (RI j=i;j<=n;j+=i) frac[j].push_back(i);
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (init(1000000),scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d",&n),ans=0,i=1;i<=n;++i)
		scanf("%d",&a[i]),pos[a[i]].push_back(i);
		for (top=0,i=1;i<=n;++i)
		{
			while (top&&a[i]<=a[stk[top]]) --top;
			if (!top) ll[i]=0; else ll[i]=stk[top]; stk[++top]=i;
		}
		for (top=0,i=n;i;--i)
		{
			while (top&&a[i]<=a[stk[top]]) --top;
			if (!top) lr[i]=n+1; else lr[i]=stk[top]; stk[++top]=i;
		}
		for (top=0,i=1;i<=n;++i)
		{
			while (top&&a[i]>a[stk[top]]) --top;
			if (!top) gl[i]=0; else gl[i]=stk[top]; stk[++top]=i;
		}
		for (top=0,i=n;i;--i)
		{
			while (top&&a[i]>=a[stk[top]]) --top;
			if (!top) gr[i]=n+1; else gr[i]=stk[top]; stk[++top]=i;
		}
		for (i=1;i<=n;++i)
		{
			for (int x:frac[a[i]])
			{
				if (idx[x]>0)
				{
					j=pos[x][idx[x]-1]; if (gl[i]<j&&i<lr[j])
					ans+=1LL*(j-max(gl[i],ll[j]))*(min(gr[i],lr[j])-i);
				}
				if (idx[x]<pos[x].size())
				{
					j=pos[x][idx[x]]; if (j<gr[i]&&ll[j]<i)
					ans+=1LL*(i-max(gl[i],max(ll[j],idx[x]>0?pos[x][idx[x]-1]:0)))*(min(gr[i],lr[j])-j);
				}
			}
			++idx[a[i]];
		}
		for (i=1;i<=n;++i) pos[a[i]].clear(),idx[a[i]]=0;
		printf("%lld\n",ans);
	}
	return 0;
}

F. Almost Sorted

有点抽象的状压DP

考虑从左到右构造\(q\),考虑若\(\exist i\)满足\(q_i=p_j\),则称\(p_j\)已经被使用了

假设当前最大的使用的数为\(m\),则大于\(m\)的数都没被使用,而小于\(m-k\)的数都被使用了

换句话说,我们可以用一个状压来表示\(m-k\sim m-1\)的数使用与否的状态

\(f_{i,s}\)表示使用的最大的数为\(i\)\(i-k-1\)之前的数都使用时,\(i-k\sim i-1\)\(k\)个数的状态为\(s\)\(s\)\(0\)的位表示已经使用, 并且低位为靠近\(i\)的位)时的最小逆序对数

转移的话考虑找出\(fst\)为最小的未被操作的数,则下一个能选的数为\(fst\sim fst+k\)中还没被使用的数

枚举下一个可以选的数\(j\),计算新产生的逆序对个数即可转移

具体实现细节较多,参考了jiangly的代码,总复杂度\(O(n \cdot 2^k \cdot k \cdot (k + \log n))\)

#include<cstdio>
#include<iostream>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
const int N=5005,S=1<<8,INF=1e9;
int n,k,f[N][S],pos[N],p[N],g[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j,s,x; for (scanf("%d%d",&n,&k),i=0;i<n;++i)
	scanf("%d",&p[i]),--p[i],pos[p[i]]=i;
	for (i=0;i<n;++i) for (j=0;j+k<i;++j) g[i]+=pos[j]>pos[i];
	for (i=0;i<n;++i) for (j=0;j<S;++j) f[i][j]=INF;
	for (i=0;i<n;++i)
	{
		if (i<=k) f[i][(1<<i)-1]=0;
		for (s=(1<<k)-1;s>=0;--s) if (f[i][s]!=INF)
		{
			
			int fst=s>0?i-1-__lg(s):i+1;
			for (j=fst;j<=fst+k&&j<n;++j)
			{
				if (j==i||(j<i&&(~s>>(i-j-1)&1))) continue;
				int val=f[i][s]+g[j],mx=max(i,j),ns=s<<(mx-i);
				if (j>i) ns|=(1<<(j-i-1))-1; else ns^=(1<<(i-j-1));
				for (x=max(j-k,0);x<=i;++x) if (x!=j)
				if (x==i||(~s>>(i-x-1)&1)) val+=pos[x]>pos[j];
				f[mx][ns]=min(f[mx][ns],val);
			}
		}
	}
	return printf("%d",f[n-1][0]),0;
}

Postscript

我就不应该在凌晨写代码的,现在头脑亢奋感觉睡不着觉了啊QAQ

posted @ 2022-09-26 22:58  空気力学の詩  阅读(487)  评论(0编辑  收藏  举报