IOI2020第一轮选拔模拟6

今天的题都比较玄学。

T1:首先我们把k个1之间的距离算出来,然后会发现它们一定是n/k或者n/k+1。否则的话如果我们移动了一个1之后,就会出现新的距离,这样就一定不能满足循环同构了。

在知道了这个性质之后,我们将n/k记为0,n/k+1记为1。那么我们发现在一个在原串中交换10位置,就相当于将原来距离蔚n/k和n/k+1的变成n/k+1和n/k的,也就是说在新串上交换了10。这样我们就将原问题转换成了一个更小的子问题。在这里,n变成了k,而k变成了n%k。我们可以递归处理。

然后就到了这题的关键部分:怎样还原出最终答案。

首先在递归到k=1时,我们就构造出l个串的最初模式。假设l=5,n=3,那么这l个串的最初模式是这样的:

100

010

001

100

010

接着就要往上还原。

我们先根据上一次的第1个串,还原出当前的第1个串。然后再根据上一次两个相邻的串之间的差异来还原接下来的l-1个串。

具体来说,就是每次我们要固定一个移动的方向(左移或右移),这个方向是每一次都要变的。在知道了方向之后,我们就直接找出上一次相邻两个串之间的不同,然后判断出它们之间的不同相当于当前的串的哪一个位置移动了,接着直接移动就好了。

注意要开一个数组记录上一次的串的每个位置对应的是当前串的哪一个1,这个数组要跟着移动。

固定方向的目的就在于保证当前的串一定是可以由相邻的串移动一个位置得到的,最后输出的时候要根据最后一个移动的方向来判断是正序输出还是倒序输出。

其实这题很难说清楚,贴一下代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#define MAXN 110

int a[MAXN][MAXN],b[MAXN][MAXN],c[MAXN],w[MAXN],n,l,k,T,ans,type;
int dg(int n,int k)
{
	int i,j,s,len1,len2,r;
	if(k==0){ans=0;return 0;}
	if(ans==0)return 0;
	if(k==1)
	{
		memset(a,0,sizeof(a));
		for(i=0;i<l;i++)a[i+1][i%n]=1;
		type=1;
		return 0;
	}
	dg(k,n%k);
	for(i=1;i<=l;i++)
		for(j=0;j<k;j++)b[i][j]=a[i][j];
	s=-1;
	len1=(n-k)/k;len2=(n-k)/k+1;
	for(i=0;i<k;i++)
		if(a[1][i]==0)
		{
			s=(s+1)%n;c[s]=1;
			for(j=1;j<=len1;j++){s=(s+1)%n;c[s]=0;}
		}
		else
		{
			s=(s+1)%n;c[s]=1;
			for(j=1;j<=len2;j++){s=(s+1)%n;c[s]=0;}
		}
	for(i=0;i<n;i++)a[1][i]=c[i];
	///////////////////////////////////////////////////////////
	s=-1;
	for(i=0;i<n;i++)
		if(a[1][i]==1){s++;w[s]=i;}
	for(r=2;r<=l;r++)
	{
		for(i=0;i<n;i++)a[r][i]=a[r-1][i];
		if(type==1)
			for(i=0;i<k;i++)
				if(b[r-1][i]!=b[r][i]&&b[r-1][i]==1&&b[r-1][(i+1)%k]==0)
				{
					j=w[(i+1)%k];
					a[r][(j-1+n)%n]=1;a[r][j]=0;
					w[(i+1)%k]=(j-1+n)%n;
				}
		if(type==2)
			for(i=0;i<k;i++)
				if(b[r-1][i]!=b[r][i]&&b[r-1][i]==0&&b[r-1][(i+1)%k]==1)
				{
					j=w[(i+1)%k];
					a[r][j]=0;a[r][(j+1)%n]=1;
					w[(i+1)%k]=(j+1)%n;
				}
	}
	if(type==1)type=2;
	else type=1;
}
int main()
{
int i,j,tf,s1,s2;
scanf("%d",&T);
while(T>=1)
{
	scanf("%d %d %d",&n,&k,&l);
	ans=1;
	dg(n,k);
	if(ans==1)
	{
		printf("YES\n");
		if(type==1)
			for(i=1;i<=l;i++)
			{
				for(j=0;j<n;j++)printf("%d",a[i][j]);printf("\n");
			}
		else
			for(i=l;i>=1;i--)
			{
				for(j=0;j<n;j++)printf("%d",a[i][j]);printf("\n");
			}
	}
	else printf("NO\n");
	T--;
}
}

T2:这又是一道很难解释的题。

对于每一个串s,求出一个最短前缀x使得x^{\infty }<s。然后设z为x^{y}+z=s。(在这里xyz都为字符串,x^{y}就是将x重复y次)。

接着我们按x^{\infty }作为第一关键字排升序,将y+U作为第二关键字排降序。(用U的目的是U比ACGT都大)

这样我们就确定了串的连接顺序,接下来按照"保证存在一种1~n连接的最优解"的方案做就好了。具体做法就是最后一个串只取第一个字符,然后倒着枚举串和串的长度,贪心取最小的。

做法到这里就讲完了。至于为什么按这种方法排序得出的就是最优的连接顺序,我们可以想一想,当x^{\infty }<s时,对于两个相同x的串,我们将一个的y插在另一个的x之前一定不是最优的。所以我们对于相同的x的串只插入x,这就是为什么我们要按x^{\infty }为第一关键字排升序。接下来可能存在的一种情况就是一种x的y接在了它后面的x之前,那么我们一定是从同种x中选y最小的是最优的。至于在y后面加上U来排序,是因为当一个y为另一个y的前缀时,选择包含另一个的那个y取比较一定是不会变劣的。

最后要注意的细节就是,在比较x^{\infty }<s时,要在s后面加上一个U。

发布了149 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/chiyankuan/article/details/103541759