Codeforces Round #751 (Div. 1 & 2) Solutions

题目能自己找吧。

CF1602A Two Subsequences

注意到,令 \(s,t\) 为字符串,其中 \(t\) 非空串,那么有 \(s < st\)。因此答案的字符串 \(a\) 一定是单个字符 \(c\) 构成,且 \(c\) 是字符串 \(s\) 的最小字符。输出 \(c\) 和去掉一个 \(c\) 之后的 \(s'\) 即可。

#include<bits/stdc++.h>
using namespace std;
char s[105];
int n;
void Solve()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	char minn='z';
	for(int i=1;i<=n;++i)	minn=min(minn,s[i]);
	putchar(minn);
	putchar(' ');
	for(int i=1;i<=n;++i)
	{
		if(minn==s[i])	minn=0;
		else	putchar(s[i]);
	}
	puts("");
}
int main(){
	int T;
	scanf("%d",&T);
	while(T-->0)	Solve();
	return 0;
}

CF1602B Divine Array

数据范围很小。猜测在 \(O(n)\) 次变化之后整个序列就不会变化了。有一个更紧的上界是 \(O(\log n)\),证明如下:

如果两种数出现次数相同,在一次变化之后这两种数会变成一种数,称这两种数可以合并。

一个数组如果不存在可合并的数就不会改变了。并且每次合并后这个新数的出现次数至少翻倍,那么最多在 \(O(\log n)\) 次操作后就不会改变。

但是 who cares,,,能过就行。

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
typedef long long LL;
typedef pair<int,int> P;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(int x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
int a[2005],mat[2005][2005],n,t[2005];
void Solve()
{
	n=read();
	for(int i=1;i<=n;++i)	a[i]=read();
	for(int i=1;i<=n;++i)	mat[0][i]=a[i];
	for(int i=1;i<=n;++i)
	{
		memset(t,0,sizeof t);
		for(int j=1;j<=n;++j)	++t[mat[i-1][j]];
		for(int j=1;j<=n;++j)	mat[i][j]=t[mat[i-1][j]];
	}
	int T=read();
	while(T-->0)
	{
		int x=read(),k=read();
		write(mat[min(k,n)][x]);
		puts("");
	}
}
int main(){
	int T=read();
	while(T-->0)	Solve();
	return 0;
}

CF1601A Array Elimination

注意到位运算上,每一位独立。把每一位拆下来,以第 \(i\) 位为例,每次操作可以消掉 \(k\) 个或 \(0\)\(i\) 位上的 \(1\)。因此答案是所有数每位上 \(1\) 出现次数的 \(\gcd\)。特殊的,如果序列全是 \(0\)\(k\) 可以取 \([1,n]\) 里的任意整数。

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
typedef long long LL;
typedef pair<int,int> P;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(int x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
int n,a[200005];
void Solve()
{
	n=read();
	for(int i=1;i<=n;++i)	a[i]=read();
	for(int i=1;i<=n;++i)	if(a[i])	goto aborted;
	for(int i=1;i<=n;++i)	write(i),putchar(i==n?'\n':' ');
	return ;
	aborted:;
	int st=0;
	for(int i=0;i<30;++i)
	{
		int x=0;
		for(int j=1;j<=n;++j)	x+=(a[j]>>i)&1;
		st=gcd(x,st);
	}
	for(int i=1;i<=st;++i)	if(st%i==0)	write(i),putchar(' ');
	puts("");
}
int main(){
	int T=read();
	while(T-->0)	Solve();
	return 0;
}

gap1

CF1601B Frog Traveler

人生苦短,,,我写线段树优化建图。

对每一个点开一个虚点表示滑下去之前的点就行了。

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
typedef long long LL;
typedef pair<int,int> P;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(int x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
struct Edge{
	int t,v;
	Edge(int T=0,int V=0){t=T,v=V;}
	bool operator < (Edge ano) const {return v>ano.v;}
};
vector<Edge> G[2000005];
int n,a[300005],b[300005],cnt,id[1200005];
#define Mm int mid=(l+r)>>1
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
void build(int l,int r,int now)
{
	id[now]=++cnt;
	if(l==r)
	{
		G[id[now]].push_back(Edge(l,0));
		return ;
	}
	Mm;
	build(l,mid,lc(now)),build(mid+1,r,rc(now));
	G[id[now]].push_back(Edge(id[lc(now)],0));
	G[id[now]].push_back(Edge(id[rc(now)],0));
}
void modify(int l,int r,int now,int x,int y,int p)
{
	if(x<=l && r<=y)
	{
		G[p].push_back(Edge(id[now],1));
		return ;
	}
	Mm;
	if(x<=mid)	modify(l,mid,lc(now),x,y,p);
	if(mid<y)	modify(mid+1,r,rc(now),x,y,p);
}
#undef Mm
#undef lc
#undef rc
int dis[2000005],pre[2000005];
void Dijkstra()
{
	memset(dis,63,sizeof dis);
	dis[n]=0;
	priority_queue<Edge> Q;
	Q.push(Edge(n,0));
	while(!Q.empty())
	{
		Edge pt=Q.top();
		Q.pop();
		if(pt.v>dis[pt.t])	continue;
		int u=pt.t;
		for(auto st:G[u])
		{
			int v=st.t,w=st.v;
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				Q.push(Edge(v,dis[v]));
				pre[v]=u;
			}
		}
	}
}
int main(){
	n=read();
	for(int i=1;i<=n;++i)	a[i]=read();
	for(int i=1;i<=n;++i)	b[i]=read();
	for(int i=1;i<=n;++i)	G[i].push_back(Edge(i+b[i]+n,0));
	cnt=2*n;
	build(1,n,1);
	for(int i=1;i<=n;++i)	modify(1,n,1,max(1,i-a[i]),i,i+n);
	++cnt;
	for(int i=1;i<=n;++i)	if(a[i]==i)	G[i+n].push_back(Edge(cnt,1));
	Dijkstra();
	int ans=1e9;
	for(int i=1;i<=n;++i)	if(a[i]==i)	ans=min(ans,dis[i+n]+1);
	if(ans>=1e8)
	{
		puts("-1");
		return 0;
	}
	write(ans),puts("");
	stack<int> S;
	int now=cnt;
	while(now!=2*n)
	{
		if(now<=n)	S.push(now);
		now=pre[now];
	}
	while(!S.empty())	write(S.top()),putchar(' '),S.pop();
	puts("0");
	return 0;
}

gap2

CF1601C Optimal Insertion

显然 \(b\) 从小到大顺序插入最优。证明如下。

假设在两个位置先后插入了 \(x,y\),其中 \(x < y\)。交换 \(x,y\) 之后,多出了 \((y,x)\) 这一个逆序对,并且答案不会变小。因此顺序插入最优。

当然你可以看样例看出来。

然后考虑构造出在 \(a\) 中插入 \(b\) 后新形成的序列 \(c\),直接计算其逆序对个数。注意到我们已知 \(b\) 按顺序插入,没必要管插入的具体顺序,只需要知道是放在 \(a\) 的哪个元素之前就行了,并且定义 \(p_i\) 表示 \(b_i\) 放在 \(a_{p_i}\) 之前。

注意到我们还可以放在 \(a\) 的最后,新加入一个极大值即可。

然后单调性分治,我们定义 Solve(L,R,l,r) 表示 \(b_{l,\cdots ,r}\) 要插入到 \(a_{L,\cdots ,R}\) 之前。令 \(mid = \left\lfloor \dfrac{l+r}{2} \right\rfloor\)我们确定了 \(b_{mid}\) 的位置之后,也确定剩下两块 \(b\) 插入的位置的范围。找到 \(b_{mid}\) 插入的最优位置可以直接 \(O(n)\),时间复杂度显然是 \(O(n \log m)\) 的。

然后就做完了呗……实现可以看代码。一定要清空干净。

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
typedef long long LL;
typedef pair<LL,LL> P;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
LL read()
{
	LL x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(LL x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
LL lowbit(LL x){return x&(-x);}
LL n,m,a[1000005],b[1000005],B[2000005],len,p[1000005],c[2000005];
struct binaryIndexedTree{
	LL tr[2000005];
	void clear(LL up){for(LL i=0;i<=up;++i)	tr[i]=0;}
	void modify(LL x,LL val){for(LL i=x;i<=len;i+=lowbit(i))	tr[i]+=val;}
	LL query(LL x){LL ans=0;for(LL i=x;i;i^=lowbit(i))	ans+=tr[i];return ans;}
}bit;
void Divide(LL L,LL R,LL l,LL r)
{
	if(l>r)	return ;
	LL mid=(l+r)>>1,val=b[mid],inv=0;
	for(LL i=L;i<=R;++i)	if(val>a[i])	++inv;
	LL ret=inv,pos=L;
	for(LL i=L+1;i<=R;++i)
	{
		if(val<a[i-1])	++inv;
		else if(val>a[i-1])	--inv;
		if(inv<ret)	inv=ret,pos=i;
	}
	p[mid]=pos;
	Divide(L,pos,l,mid-1),Divide(pos,R,mid+1,r);
}
void Solve()
{
	n=read(),m=read();
	for(LL i=1;i<=n;++i)	a[i]=read();
	for(LL i=1;i<=m;++i)	b[i]=read();
	for(LL i=1;i<=n;++i)	B[i]=a[i];
	for(LL i=1;i<=m;++i)	B[i+n]=b[i];
	sort(b+1,b+1+m);
	sort(B+1,B+1+n+m);
	len=unique(B+1,B+1+n+m)-B-1;
	for(LL i=1;i<=n;++i)	a[i]=lower_bound(B+1,B+1+len,a[i])-B;
	for(LL i=1;i<=m;++i)	b[i]=lower_bound(B+1,B+1+len,b[i])-B;
	a[++n]=++len;
	Divide(1,n,1,m);
	LL cnt=0,pos=1;
	for(LL i=1;i<=n;++i)
	{
		while(pos<=m && p[pos]==i)	c[++cnt]=b[pos++];
		c[++cnt]=a[i];
	}
	bit.clear(len);
	LL ans=0;
	for(LL i=1;i<=n+m;++i)
	{
		ans+=bit.query(len)-bit.query(c[i]);
		bit.modify(c[i],1);
	}
	write(ans),puts("");
}
int main(){
	LL T=read();
	while(T-->0)	Solve();
	return 0;
}

有一个疑点是,\(b_mid\) 插入的最优位置可以有很多个,为什么没有影响?

CF1601D Difficult Mountain

人类智慧题……瞎猜了几个贪心交上去竟然过了。

本来这种题的套路是按某种方式排序之后 dp(可能需要数据结构优化),但是这个题可以直接排了过。

将人按 \(\max(a_i,s_i)\) 为第一关键字,\(s_i\) 为第二关键字,排序,然后顺序判断一个人能否爬山,能爬就爬。可以证明答案最优。

但证明是真不会……还不如去写了 \(O(n \log n)\) 的做法……找个时间补一补。

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
typedef long long LL;
typedef pair<int,int> P;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(int x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
struct node{
	int s,a;
	void scan(){s=read(),a=read();}
	bool operator < (node ano) const {return max(s,a)<max(ano.s,ano.a) || (max(s,a)==max(ano.s,ano.a) && s<ano.s);}
}clm[500005];
int n,d;
int main(){
	n=read(),d=read();
	for(int i=1;i<=n;++i)	clm[i].scan();
	sort(clm+1,clm+1+n);
	int ans=0;
	for(int i=1;i<=n;++i)
	{
		if(d<=clm[i].s)
		{
			++ans;
			d=max(d,clm[i].a);
		}
	}
	write(ans);
	return 0;
}

CF1601E Phys Ed Online

询问相当于从 \(l\) 开始,\(l,l+k,l+2k, \cdots\) 取一次 \([l,l]\) 中的最小值,\([l,l+k]\) 中的最小值,\([l,l+2k]\) 中的最小值……然后累加。

注意到这些位置在模 \(k\) 的意义下同余,并且每次新增加的取值个数有 \(k+1\) 个(我们将上一个端点也包含),我们定义 \(b_i\)\([i-k,i]\) 中的最小值。

然后考虑倍增,定义 \(nxt_{i,j}\) 表示从 \(i\) 开始,第 \(2^j\)\(b\) 值小于 \(b_i\) 的位置。求 \(nxt_{i,0}\) 可以单调栈。有了 \(nxt\) 求值也不会很难,定义 \(cst_{i,j}\) 为从 \(i-k\)(这里要非常注意,,)跳至 \(j\) 需要的最少花费。

算了简单题可以看代码。

#include<bits/stdc++.h>
using namespace std;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
typedef long long LL;
typedef pair<LL,LL> P;
#define mp make_pair
LL read()
{
	LL x=0,f=1;
	char c=getchar();
	while(c<'0' || c>'9')	f=(c=='-'?-1:f),c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x*f;
}
void write(LL x)
{
	if(x<0)	putchar('-'),x=-x;
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
LL n,q,K,a[300005],st[22][300005],lgs[300005],blk[300005],nxt[22][300005],cst[22][300005];
LL query(LL l,LL r)
{
	LL k=lgs[r-l+1];
	return min(st[k][l],st[k][r-(1<<k)+1]);
}
int main(){
	n=read(),q=read(),K=read();
	for(LL i=2;i<=n;++i)	lgs[i]=lgs[i>>1]+1;
	for(LL i=1;i<=n;++i)	st[0][i]=a[i]=read();
	for(LL i=1;i<=20;++i)	for(LL j=1;j+(1<<i)-1<=n;++j)	st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
	for(LL i=1;i<=n;++i)	blk[i]=query(max(i-K,1ll),i);
	for(LL i=1;i<=K;++i)
	{
		stack<LL> S;
		for(LL j=i;j<=n;j+=K)
		{
			while(!S.empty() && blk[j]<blk[S.top()])	nxt[0][S.top()]=j,S.pop();
			S.push(j);
		}
	}
	for(LL i=1;i<=20;++i)	for(LL j=1;j+(1<<i)-1<=n;++j)	nxt[i][j]=nxt[i-1][nxt[i-1][j]];
	for(LL i=1;i<=n;++i)	cst[0][i]=(nxt[0][i]-i)/K*blk[i];
	for(LL i=1;i<=20;++i)	for(LL j=1;j+(1<<i)-1<=n;++j)	cst[i][j]=cst[i-1][j]+cst[i-1][nxt[i-1][j]];
	while(q-->0)
	{
		LL l=read(),r=read(),ans=a[l],now=l+K;
		if(now<=r)	for(LL j=20;~j;--j)	if(nxt[j][now]<=r && nxt[j][now])	ans+=cst[j][now],now=nxt[j][now];
		if(now<=r)	ans+=(r-now+K)/K*blk[now];
		write(ans),puts("");
	}
	return 0;
}

gap3

CF1601F Two Sorts

定义 \(b_i\)\(a\) 的逆变换(相当于 \(i\) 的排名),显然有 \(a_{b_i} = b_{a_i} = i\)。注意到 \(b_i\) 也是排列,将 \(i\) 替换成 \(b_i\) 结果不变,答案就是:

\[\left(\sum_{i=1}^n ((b_i-i) \bmod 998244353)\right) \bmod 10^9+7 \]

下面的东西一定要对着代码看,真的很难讲懂。

然后考虑怎么算这个东西。我们将一个数 \(x\) 掰开,拆成 \(p,q\) 两部分,然后分两部分处理,不难发现 \(p,q\) 位数相等,或者说 \(q\) 位数为数据范围位数的二分之一(即 \(\sqrt n = 10^6\))的时候最优。那假设 \(q\) 是一个可以有前导零的位数最多为六的整数,\(p\) 不能存在前导 \(0\)。我们先处理 \(q\)(可以直接 \(O(10^7)\) 做,按字典序枚举 \(q\),这个过程可以用搜索),再处理 \(p\)(类似的用搜索)。

定义两个东西 \(rk_i,rk'_i\),前者表示在 \(1 \sim n\) 中,\(i\) 的排名是多少和在 \(1 \sim 10^7-1\) 中,\(i\) 的排名是多少。注意到处理完这两个东西 \(rk_p\)\(rk'_q\) 之后,我们可以求出 \(rk_{\overline{pq}} = rk_p + rk'_q\)。同时,因此我们可以将 \(i = \overline{pq}\) 的贡献 \(rk_i - i\) 拆成两部分,即 \(((rk_p - 10^6 \times p)+(rk'_q - q)) \bmod 998244353\)

然后比较烦人的是,这个东西还要去取模,,,但是注意到 \((rk_p - 10^6 \times p)\) 固定,我们要管理的主要是 \((rk'_q - q)\),其值要么减去 \(998244353\) 要么不减。我们处理完 \(rk'_q - q\) 的值之后,将位数相同的 \(q\)\(rk'_q - q\) 的值排序,然后在算贡献的时候二分,求出需要减去多少个 \(998244353\) 即可。

然后细节不多,实现很妙,一定要仔细看。很难见到的一道看代码能比看题解更清楚的题目。

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
typedef long long LL;
typedef pair<LL,LL> P;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
LL read()
{
	LL x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(LL x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const LL MOD1=998244353,MOD2=1e9+7;
vector<LL> G[7];
LL n,rnk,sum[7],ans,pw[7],siz[7];
void dfs(LL dgt,LL val)
{
	++rnk;
	G[dgt].push_back((rnk-val+MOD1)%MOD1);
	if(dgt==6)	return ;
	for(LL i=0;i<=9;++i)	dfs(dgt+1,val*10+i);
}
void red(LL dgt,LL val)
{
	if(val>n)	return ;
	if(dgt>=1)
	{
		if(val*1000000>n/10 && (val+1)*1000000-1<=n)
		{
			for(LL i=0;i<=6;++i)
			{
				LL st=(rnk%MOD1-val*pw[i]%MOD1+MOD1)%MOD1,pos=lower_bound(G[i].begin(),G[i].end(),MOD1-st)-G[i].begin();
				ans=(ans+st*siz[i]+sum[i]-MOD1*(siz[i]-pos))%MOD2;
			}
			for(LL i=0;i<=6;++i)	rnk+=siz[i];
			return ;
		}
		++rnk,ans=(ans+(rnk%MOD1-val%MOD1+MOD1)%MOD1)%MOD2;
	}
	for(LL i=!dgt;i<=9;++i)	red(dgt+1,val*10+i);
}
int main(){
	n=read();
	dfs(0,0);
	pw[0]=1;
	for(LL i=0;i<=6;++i)
	{
		if(i)	pw[i]=pw[i-1]*10;
		sort(G[i].begin(),G[i].end());
		for(auto st:G[i])	sum[i]=(sum[i]+st)%MOD2;
		siz[i]=LL(G[i].size());
	}
	rnk=0;
	red(0,0);
	write(ans);
	return 0;
}
原文地址:https://www.cnblogs.com/amagaisite/p/15563743.html