zl程序教程

您现在的位置是:首页 >  其它

当前栏目

Codeforces Round #542 (Div. 1)

Codeforces div round
2023-09-11 14:14:40 时间

Codeforces Round #542 (Div. 1)

似乎是一周前的比赛了QwQ,然而立过flag要每周写一场来着,就来补一补QwQ。

A1/A2. Toy Train

翻译

\(n\)个点排成一圈,有\(m\)个货物,第\(i\)个货物要从\(a_i\)运到\(b_i\),在每个车站只能装一个货物,求从第\(i\)个车站出发,运送完所有货物的最短的时间。

题解

如果当前有多个元素,那么必须多走一圈回来拿。所以需要考虑的只有每个车站的最后一个被拿走的东西,显然这个东西就是目标位置离当前位置最近的那个货物。
然后大力讨论一下就好了。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 5050
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int n,m,cnt[MAX],d[MAX];
int main()
{
	n=read();m=read();
	for(int i=1;i<=n;++i)d[i]=n+1;
	for(int i=1;i<=m;++i)
	{
		int l=read(),r=read();if(r<l)r+=n;
		cnt[l]+=1;d[l]=min(d[l],r-l);
	}
	int mx=0;for(int i=1;i<=n;++i)mx=max(mx,cnt[i]);
	for(int i=1;i<=n;++i)if(cnt[i]<max(1,mx-1))d[i]=0;
	for(int i=1;i<=n;++i)
	{
		int ans=(mx-1)*n,mxx=0;
		for(int j=1;j<=n;++j)
			if(cnt[j]==mx)
			{
				int dis=i<=j?j-i:j-i+n;
				dis+=d[j];mxx=max(mxx,dis);
			}
			else if(cnt[j]==mx-1)
			{
				int dis=j<i?i-j:i-j+n;
				mxx=max(mxx,d[j]-dis);
			}
		printf("%d ",ans+mxx);
	}
	puts("");return 0;
}

B. Wrong Answer

翻译

给了你一个假贪心,你要构造一组数据把它卡掉,并且使得这组数据的结果和贪心的结果之差恰好为\(k\)

题解

不难发现可以让前面构造一段连续大于\(0\)的数,中间构造一段负数,然后再构造一段正数,贪心的结果就是两段正数的和乘上长度,答案是全局的和乘上长度,
那么令前两个数是\(1,-2\),设一共有\(n\)个数,剩下的\(n-2\)个数的和是\(S\)
那么有等式:\((S-1)*n-S*(n-2)=k\),解出来\(2S=k+n\)
那么枚举所有的\(n\),判断一下能否放\(n-2\)个数使得他们的和为\(S\)即可。

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
	int k;scanf("%d",&k);
	for(int n=1;n<=1998;++n)
		if((k+n)%2==0)
			if((k+n+2+1999999)/2000000<=n)
			{
				printf("%d\n",n+2);
				printf("1 -2 ");
				int S=(k+n+2)/2;
				for(int j=1;j<n;++j)
					if(j==n-1&&S<=1000000)printf("%d ",S/2),S-=S/2;
					else printf("1000000 "),S-=1e6;
				printf("%d\n",S);return 0;
			}
	puts("-1");
	return 0;
}

C. Morse Code

翻译

长度为\(1,2,3,4\)的二进制串一共有\(30\)个,除了\(0011,0101,1110,1111\)之外,每一个都对应着一个字母。
现在给定一个串\(S\),对于每一个\(i\),询问\(S[1,i]\)中的所有子串中,本质不同的对应着一个英文字母串的拆分方案数。

题解

\(f[l][r]\)表示\(l,r\)这段区间的拆分方案数,显然枚举最后一个字母是什么就好了。
然后要求解的是本质不同的方案数,所以随便找个东西去去重就好了。
可以写哈希,我写的\(SAM\)

#include<iostream>
#include<cstdio>
#include<set>
using namespace std;
#define MOD 1000000007
#define MAX 3030
int m,S[MAX],f[MAX][MAX],top,ans;
bool check(int l,int r)
{
	if(r-l+1<4)return true;
	if(S[l]==0&&S[l+1]==0&&S[l+2]==1&&S[l+3]==1)return false;
	if(S[l]==0&&S[l+1]==1&&S[l+2]==0&&S[l+3]==1)return false;
	if(S[l]==1&&S[l+1]==1&&S[l+2]==1&&S[l+3]==0)return false;
	if(S[l]==1&&S[l+1]==1&&S[l+2]==1&&S[l+3]==1)return false;
	return true;
}
struct Node{int son[2],ff,len;set<int> vis;}t[MAX<<1];
int tot=1,last=1;
void extend(int c)
{
	int np=++tot,p=last;last=tot;
	t[np].len=t[p].len+1;
	while(p&&!t[p].son[c])t[p].son[c]=np,p=t[p].ff;
	if(!p)t[np].ff=1;
	else
	{
		int q=t[p].son[c];
		if(t[q].len==t[p].len+1)t[np].ff=q;
		else
		{
			int nq=++tot;
			t[nq]=t[q];t[nq].len=t[p].len+1;
			t[q].ff=t[np].ff=nq;
			while(p&&t[p].son[c]==q)t[p].son[c]=nq,p=t[p].ff;
		}
	}
}
int main()
{
	scanf("%d",&m);
	for(int i=1;i<=m;++i)scanf("%d",&S[i]);
	for(int i=m;i;--i)extend(S[i]);
	for(int i=1;i<=m;++i)
		for(int j=i;j>=max(1,i-3);--j)
			if(check(j,i))
			{
				f[j][i]=(f[j][i]+1)%MOD;
				for(int k=j-1;k;--k)
					f[k][i]=(f[k][i]+f[k][j-1])%MOD;
			}
	for(int i=1;i<=m;++i)
	{
		for(int j=i,u=1;j;--j)
		{
			u=t[u].son[S[j]];
			if(t[u].vis.count(i-j+1))continue;
			t[u].vis.insert(i-j+1);
			ans=(ans+f[j][i])%MOD;
		}
		printf("%d\n",ans);
	}
	return 0;
}

D. Isolation

翻译

把一个数组分成不相交的若干段,使得每一段中出现了恰好\(1\)次的数的个数至多是\(k\)个。
求划分方案数。

题解

\(f[i]\)表示前\(i\)个数的划分方案数。
每次考虑一个\(j\),如果\([j+1,i]\)是合法划分,那么\(f[j]\rightarrow f[i]\)
考虑一个数值\(v\)什么时候会产生贡献,假设在\(i\)左侧第一次出现的位置是\(x\),第二次出现的位置是\(y\),那么当\(j\in [y+1,x]\)时,就会产生\(1\)的贡献。
对于每个块维护一个区间加法标记以及每个权值的\(f\)的和,然后每次暴力维护区间加法就好啦。
时间复杂度\(O(n\sqrt n)\)

#include<iostream>
#include<cstdio>
using namespace std;
#define MOD 998244353
#define MAX 100100
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
const int blk=800,py=MAX-10;
int S[130][MAX*3],tag[130],Sum,val[MAX],bel[MAX];
int n,k,a[MAX],lst[MAX],pre[MAX],f[MAX];
void Modify(int l,int r,int w)
{
	for(int &i=l;i<=r&&bel[i-1]==bel[i];++i)
	{
		int K=k-tag[bel[i]];
		add(S[bel[i]][val[i]+py],MOD-f[i-1]);
		if(val[i]+tag[bel[i]]>=0&&val[i]<=K)add(Sum,MOD-f[i-1]);
		val[i]+=w;add(S[bel[i]][val[i]+py],f[i-1]);
		if(val[i]+tag[bel[i]]>=0&&val[i]<=K)add(Sum,f[i-1]);
	}
	for(int &i=r;i>=l&&bel[i+1]==bel[i];--i)
	{
		int K=k-tag[bel[i]];
		add(S[bel[i]][val[i]+py],MOD-f[i-1]);
		if(val[i]+tag[bel[i]]>=0&&val[i]<=K)add(Sum,MOD-f[i-1]);
		val[i]+=w;add(S[bel[i]][val[i]+py],f[i-1]);
		if(val[i]+tag[bel[i]]>=0&&val[i]<=K)add(Sum,f[i-1]);
	}
	if(l>r)return;
	for(int i=bel[l];i<=bel[r];++i)
	{
		int R=k-tag[i],L=0-tag[i];
		if(w==1)add(Sum,MOD-S[i][R+py]),add(Sum,S[i][L-1+py]);
		else add(Sum,MOD-S[i][L+py]),add(Sum,S[i][R+1+py]);
		tag[i]+=w;
	}
}
int main()
{
	n=read();k=read();
	for(int i=1;i<=n;++i)a[i]=read(),pre[i]=lst[a[i]],lst[a[i]]=i;
	for(int i=0;i<=n+1;++i)bel[i]=(i+blk-1)/blk;
	f[0]=Sum=S[1][py]=1;
	for(int i=1;i<=n;++i)
	{
		int a=pre[i],b=pre[a];
		Modify(a+1,i,1);if(a)Modify(b+1,a,-1);
		f[i]=Sum;add(Sum,f[i]);
		add(S[bel[i+1]][py],f[i]);
	}
	printf("%d\n",f[n]);
	return 0;
}

E. Legendary Tree

翻译

有一棵\(n\)个节点的树,每次可以询问两个点集\(S,T\)以及一个单点\(v\),交互库会返回有多少对\((s,t)\)满足\(s\in S,t\in T\),且\(s,t\)的路径经过了\(v\)
询问次数不超过\(11111\)

题解

首先随便钦定一个点作为根节点,假装是\(1\)
然后询问\(S=\{1\},T=U-S-\{v\},v\),其中\(U\)是全集,这样子就可以知道每棵子树的大小。
我们按照子树大小从小往大处理,显然就是对于每一个点,在子树大小比他小的点中找它的儿子。如果找到了儿子可以直接把儿子在前面的点集中删去,那么每次把点集分成两半,如果左边有就往左边走,如果右边有就往右边走。
不妨令每次恰好只找一个儿子,可以证明只需要\(log\)次询问就可以确定一个儿子。
所以这样子的复杂度不会超过\(nlogn\)

然后我自己还有一个奇怪的想法,不知道能不能优化:
把所有点按照子树大小排序,显然子树最大的那个点\(x\)一定是\(1\)号点的一个儿子。
然后对于个点询问\(S=\{1\},T=\{u\},v=x\),那么就可以知道一个点是不是\(x\)的儿子。
然后把这棵子树分开就可以递归处理下去。然而这样子需要询问\(O(n^2)\)次(菊花)。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
#define MAX 505
int n,p[MAX],fa[MAX],size[MAX];
set<int> S;
bool cmp(int a,int b){return size[a]<size[b];}
void Solve(int u,set<int>::iterator it,int tot)
{
	if(tot==1){fa[*it]=u;S.erase(it);return;}
	int mid=tot>>1,x;set<int>::iterator tmp=it;
	printf("%d\n",mid);for(int i=1;i<=mid;++i,++it)printf("%d ",*it);puts("");
	printf("1\n1\n%d\n",u);fflush(stdout);
	scanf("%d",&x);if(x)Solve(u,tmp,mid);tmp=it;
	printf("%d\n",tot-mid);for(int i=mid+1;i<=tot;++i,++it)printf("%d ",*it);puts("");
	printf("1\n1\n%d\n",u);fflush(stdout);
	scanf("%d",&x);if(x)Solve(u,tmp,tot-mid);
}
int main()
{
	scanf("%d",&n);if(n==2){printf("ANSWER\n1 2\n");return 0;}
	for(int i=2;i<=n;++i)
	{
		printf("1\n%d\n%d\n",1,n-2);
		for(int j=2;j<=n;++j)if(i^j)printf("%d ",j);puts("");
		printf("%d\n",i);fflush(stdout);
		scanf("%d",&size[i]);
	}
	for(int i=2;i<=n;++i)p[i-1]=i;
	sort(&p[1],&p[n],cmp);
	for(int i=1;i<n;++i)
	{
		int u=p[i];
		if(!size[u]){S.insert(u);continue;}
		Solve(u,S.begin(),S.size());
		S.insert(u);
	}
	for(set<int>::iterator it=S.begin();it!=S.end();++it)fa[*it]=1;
	puts("ANSWER");
	for(int i=2;i<=n;++i)printf("%d %d\n",i,fa[i]);
	return 0;
}