7.构造

构造

开题顺序: \(JICKLHA\)

\(A\) CF804E The same permutation

  • 每次交换会导致逆序对奇偶性的变化,有解当且仅当 \(\dbinom{n}{2}\) 为偶数,即 \(n \bmod 4=0 \lor n \bmod 4=1\)

  • \(n \bmod 4=0\) 时,将所有元素四个视为一组,组内消耗 \(6 \times \dfrac{n}{4}\) 次交换,组间消耗 \(16 \times \dfrac{\frac{n}{4}(\frac{n}{4}-1)}{2}\) 次交换。

    • 搜索得到组内交换形如 \((1,2)(3,4)(2,3)(1,4)(1,3)(2,4)\)
    • 组间交换形如 \((a_{1},b_{1})(a_{2},b_{2})(a_{3},b_{3})(a_{4},b_{4})(a_{1},b_{2})(a_{2},b_{3})(a_{3},b_{4})(a_{4},b_{1})(a_{1},b_{4})(a_{2},b_{1})(a_{3},b_{2})(a_{4},b_{3})(a_{1},b_{3})(a_{2},b_{4})(a_{3},b_{1})(a_{4},b_{2})\)
  • \(n \bmod 4=1\) 时,需将 \(n\) 插进去消耗 \(n-1\) 次交换。

    • 将组内交换中的 \((1,2)\) 换成 \((1,n)(1,2)(2,n)\) ,将 \((3,4)\) 换成 \((3,n)(3,4)(4,n)\) 即可。
    点击查看代码
    int a[7][2]={{0,0},{1,2},{3,4},{2,3},{1,4},{1,3},{2,4}};
    int b[17][2]={{0,0},{1,1},{2,2},{3,3},{4,4},
    					{1,2},{2,3},{3,4},{4,1},
    					{1,4},{2,1},{3,2},{4,3},
    					{1,3},{2,4},{3,1},{4,2}};
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int n,i,j,k;
    	cin>>n;
    	if(n%4<=1)
    	{
    		cout<<"YES"<<endl;
    		for(i=0;i<n-(n%4);i+=4)
    		{
    			for(j=1;j<=6;j++)
    			{
    				if(j<=2&&n%4==1)  cout<<i+a[j][0]<<" "<<n<<endl;
    				cout<<i+a[j][0]<<" "<<i+a[j][1]<<endl;
    				if(j<=2&&n%4==1)  cout<<i+a[j][1]<<" "<<n<<endl;
    			}
    		}
    		for(i=0;i<n-(n%4);i+=4)
    		{
    			for(j=i+4;j<n-(n%4);j+=4)
    			{
    				for(k=1;k<=16;k++)
    				{
    					cout<<i+b[k][0]<<" "<<j+b[k][1]<<endl;
    				}
    			}
    		}	
    	}
    	else
    	{
    		cout<<"NO"<<endl;
    	}
    	return 0;
    }
    

\(B\) [AGC046E] Permutation Cover

\(C\) CF1053E Euler tour

  • 合法的答案序列首尾一定相等,先判掉。

  • 对于已经确定的位置 \(a_{l}=a_{r},l \le r\) ,其内部是一棵子树,合法当且仅当 \(r-l\) 为偶数且 \([l,r]\) 至多已经确定了 \(\frac{r-l}{2}+1\) 种元素。

  • 对于所有满足 \(a_{l}=a_{r}\) 的区间 \([l,r]\) 可以包含但不能相交,否则无解。

  • 构造流程如下。正确性我不会证。

    • 若存在 \(a_{l}=a_{r}\) 则递归处理子树并缩成一个点 \(a_{l}\) (为了方便下面的构造我们并不把这棵子树直接删去),此时假设序列中没有相同元素。具体地,若 \([l,r]\) 中出现了少于 \(\frac{r-l}{2}+1\) 种元素则用未出现过的元素填到恰好 \(\frac{r-l}{2}\) 种元素,同时还要满足下述的构造方式。
    • 若存在形如 \(x,y,0\)\(0,y,x\) 的子串则将其填成 \(x,y,x\) 并缩成一个点 \(x\) ,此时假设序列中没有相邻的 \(0\)
    • 此时序列一定形如 \(a_{l},x,0,y,0,\dots,0,z,a_{l}\) ,将所有 \(0\) 填成 \(a_{l}\)
  • 双向链表辅助进行跳跃删除。

    点击查看代码
    int a[1000010],vis[1000010],last[1000010],pre[1000010],suf[1000010],nxt[1000010],mex=1,n;
    void del(int l,int r)
    { 
    	suf[pre[l]]=suf[r];
    	pre[suf[r]]=pre[l];
    }
    int get_val()
    {
    	for(;vis[mex]==1;mex++);
    	vis[mex]=1;
    	return mex>n?-1:mex;
    }
    void merge(int l,int r,int &x)
    {
    	for(;x>=l&&suf[suf[x]]!=0&&suf[suf[x]]<=r&&a[x]==0&&a[suf[x]]!=0&&a[suf[suf[x]]]!=0;x=pre[pre[x]])//0,y,x
    	{
    		a[x]=a[suf[suf[x]]];
    		del(suf[x],suf[suf[x]]);//保留 x
    	}	
    	for(;x>=l&&suf[suf[x]]!=0&&suf[suf[x]]<=r&&a[x]!=0&&a[suf[x]]!=0&&a[suf[suf[x]]]==0;x=pre[pre[x]])//x,y,0
    	{
    		a[suf[suf[x]]]=a[x];
    		del(suf[x],suf[suf[x]]);//保留 x
    	}
    }
    bool solve(int l,int r)
    {
    	if((r-l)%2==1)  return false;
    	for(int i=l;i<=r;i=suf[i])
    	{
    		for(;nxt[i]!=0;nxt[i]=nxt[nxt[i]])
    		{
    			if(nxt[i]>r||solve(suf[i],pre[nxt[i]])==false)  return false;
    			//判断是否两个区间是否有交却不包含
    			//递归处理子树内部 suf[i] ~ pre[nxt[i]] 
    			del(suf[i],nxt[i]);//保留 i ,删除 suf[i] ~ nxt[i]
    		}
    	}
    	int sum=0,cnt=0,col=a[pre[l]];
    	for(int i=l;i<=r;i=suf[i])
    	{
    		sum+=(a[i]!=0);  cnt++;
    	}
    	if(sum>cnt/2+1)  return false;//此时不存在相同元素,可以计算出现元素数量
    	cnt=cnt/2+1-sum;
    	for(int i=l;i<=r&&cnt>=1;i=suf[i])//要求元素数量恰好等于 (r-l)/2+1
    	{
    		cnt-=(a[i]==0);
    		a[i]=(a[i]==0)?get_val():a[i];
    		if(a[i]==-1)  return false;
    	}
    	for(int i=l;i<=r;i=suf[i])  merge(l,r,i);//相邻三个一起处理
    	for(int i=l;i<=r;i=suf[i])  a[i]=(a[i]==0)?col:a[i];
    	return true;
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int flag,i;
    	cin>>n;
    	suf[0]=1;
    	for(i=1;i<=2*n-1;i++)
    	{
    		cin>>a[i];  vis[a[i]]=1;
    		pre[i]=i-1;  suf[i]=i+1;
    	}
    	flag=(a[1]!=0&&a[2*n-1]!=0&&a[1]!=a[2*n-1]);//若首尾都存在则必须相等
    	a[1]=a[2*n-1]=a[1]|a[2*n-1];
    	for(i=2*n-1;i>=1;i--)
    	{
    		nxt[i]=last[a[i]]*(a[i]!=0);
    		last[a[i]]=i;
    		flag|=(a[i]!=0&&(nxt[i]-i)%2==1);
    	}
    	if(flag==0&&solve(1,2*n-1)==true)
    	{
    		cout<<"yes"<<endl;
    		for(i=1;i<=2*n-1;i++)  cout<<a[i]<<" ";
    	}
    	else  cout<<"no"<<endl;
    	return 0;
    }
    

\(D\) [AGC006E] Rotate 3x3

\(E\) CF715D Create a Maze

\(F\) CF1276E Four Stones

\(G\) CF341E Candies Game

\(H\) [AGC030C] Coloring Torus

  • \(k \le 500\) 时只需要构造第 \(i\) 行颜色为 \(i\) 即可。

  • \(k>500\) 时发现如下斜着染色也是合法的,考虑进一步调整,通过对于方阵边长都为偶数的斜线加入新的数字使其与原有数字交替出现,总数字个数至多 \(2n\) ,可以接受。

    1 2 3 4 5
    2 3 4 5 1
    3 4 5 1 2
    4 5 1 2 3
    5 1 2 3 4
    
    点击查看代码
    int a[510][510];
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int k,n,i,j;
    	cin>>k;
    	n=min(k,500);
    	for(i=1;i<=n;i++,k--)
    	{
    		for(j=1;j<=n;j++)
    		{
    			a[j][(i+j-2+n)%n+1]=((j%2==1||k<=n)?i:k);
    		}
    	}
    	cout<<n<<endl;
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=n;j++)
    		{
    			cout<<a[i][j]<<" ";
    		}
    		cout<<endl;
    	}
    	return 0;
    }
    

\(I\) CF1495C Garden of the Sun

  • 凸显出矩阵的方格后,不会出现以下这种情况。

    X.
    .X
    
  • 考虑每三行作为一组单独考虑,然后将不连通的部分连起来。具体地,先将 \(1,4,\dots ,3k+1\) 行都变成 X ,此时这些行上下分别能连通到其他行,但不保证全局连通。

  • 根据不相邻的限制条件后每一部分选第一列或第二列变成 X 即可。

  • 对第 \(n\) 行和 \(m=1\) 的情况特殊处理。

    点击查看代码
    char c[510][510],ans[510][510];
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int t,n,m,x,i,j,k;
    	scanf("%d",&t);
    	for(k=1;k<=t;k++)
    	{
    		scanf("%d%d",&n,&m);
    		for(i=1;i<=n;i++)
    		{
    			for(j=1;j<=m;j++)
    			{
    				scanf(" %c",&c[i][j]);
    				ans[i][j]=(i%3==1||m==1)?'X':c[i][j];
    			}
    		}
    		for(i=1;i<=n;i+=3)
    		{
    			if(i+2<=n&&m>=2)
    			{
    				x=((c[i+1][2]=='X'||c[i+2][2]=='X')?2:1);
    				ans[i+1][x]=ans[i+2][x]='X';
    			}
    		}
    		if(n%3==0)
    		{
    			for(i=1;i<=m;i++)
    			{
    				if(ans[n][i]=='X'&&ans[n][i-1]!='X')
    				{
    					ans[n-1][i]='X';
    				}
    			}
    		}
    		for(i=1;i<=n;i++)
    		{
    			for(j=1;j<=m;j++)
    			{
    				printf("%c",ans[i][j]);
    			}
    			printf("\n");
    		}
    	}
    	return 0;
    }
    

\(J\) [AGC029C] Lexicographic constraints

  • 考虑二分答案,设当前二分出的答案为 \(mid\) ,考虑尽可能构造符合题意的合法情况。

  • \(a_{i-1}<a_{i}\) ,直接让 \(a_{i}\)\(a_{i-1}\) 后面补 \(a_{i}-a_{i-1}\)1 即可。

  • 否则舍弃长度为 \(a_{i}-a_{i-1}\) 的后缀后将最后一个字符 \(+1\) ,若等于 \(mid\) 了则依次向前进位。最后若第一个位置产生了进位则一定不合法。

  • 暴力模拟的复杂度无法接受,考虑用栈维护不是 \(1\) 的位置,由颜色段均摊理论可知时间复杂度为 \(O(n)\)

  • 特判答案为 \(1\) 的情况。

    点击查看代码
    int a[200010];
    stack<pair<int,int> >s;
    void add(int pos,int mid)
    {
    	while(s.top().first>pos)  s.pop();
    	if(s.top().first==pos)  s.top().second++;
    	else  s.push(make_pair(pos,1));
    	if(s.size()>=2&&s.top().second==mid)  
    	{
    		s.pop();
    		add(pos-1,mid);
    	}
    }
    bool check(int mid,int n)
    {
    	while(s.empty()==0)  s.pop();
    	s.push(make_pair(0,0));
    	for(int i=2;i<=n;i++)
    	{
    		if(a[i-1]>=a[i])
    		{
    			add(a[i],mid);
    		}
    	}
    	while(s.size()>=2)  s.pop();
    	return s.top().second==0;
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int n,l=2,r=1000000000,ans=1,mid,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		ans&=(a[i]>a[i-1]);
    	}
    	if(ans==0)
    	{
    		while(l<=r)
    		{
    			mid=(l+r)/2;
    			if(check(mid,n)==true)
    			{
    				ans=mid;
    				r=mid-1;
    			}
    			else
    			{
    				l=mid+1;
    			}
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

\(K\) CF1227G Not Same

  • 脑电波题。

  • 等价于构造一个 \(n+1\)\(n\) 列的 \(01\) 矩阵使得第 \(i\)\(1\) 的个数恰好等于 \(a_{i}\)

  • \(\{ a \}\) 降序排序后从第 \(i\) 行开始往下填 \(a_{i}\)\(1\) ,如果 \(>n+1\) 了再从第 \(1\) 行开始往下填。

  • 考虑如何证明正确性。

    • 当第 \(n+1\) 行全是 \(0\) 时,显然合法。
    • 当第 \(n+1\) 行不全是 \(0\) 时,假设第 \(1 \le i<j \le n+1\) 行完全相同则有 \(\begin{cases} \forall k \in [1,i),k+a_{k}-1 \ge j \lor k+a_{k}-1<i \\ i+a_{i}-1 \ge j \\ \forall k \in (i,j),k+a_{k}-1 \ge n+1+i \lor k+a_{k}-1<j \\ j+a_{j}-1 \ge n+1+i \\ \forall k \in (j,n],k+a_{k}-1 \ge n+1+j \lor k+a_{k}-1<n+1+i \end{cases}\) ,又因为 \(n \ge a_{1} \ge a_{2} \ge \dots \ge a_{n}\) 对于 \(k \in (i,j)\) 实际有 \(k+a_{k}-1 \ge n+1+i\) 只有当 \(k \ge i+2\) 时才可能被满足,同时有 \(a_{k} \le a_{i-1}<j+2-i\) ,进一步得到有 \(k+a_{k}-1 \ge n+1+i\) 不可能被满足,得到 \(a_{j-1}-1+j-1<j\) ,即 \(a_{j-1}=1<2\) 。假设不成立。
    点击查看代码
    int id[1010],ans[1010][1010];
    pair<int,int>a[1010];
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int n,i,j;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i].first;
    		a[i].second=i;
    	}
    	sort(a+1,a+1+n,greater<pair<int,int> >());
    	for(i=1;i<=n;i++)  id[a[i].second]=i;
    	for(i=1;i<=n;i++)
    	{
    		for(j=0;j<=a[i].first-1;j++)
    		{
    			ans[(i+j-1)%(n+1)+1][i]=1;
    		}
    	}
    	cout<<n+1<<endl;
    	for(i=1;i<=n+1;i++)
    	{
    		for(j=1;j<=n;j++)  cout<<ans[i][id[j]];
    		cout<<endl;
    	}
    	return 0;
    }
    

\(L\) [AGC020D] Min Max Repetition

  • 考虑用出现次数较少的字符去隔断出现次数较多的字符,最小的连续的相同字符个数的最大值为 \(len=\left\lceil \frac{\max(a,b)}{\min(a,b)+1} \right\rceil\)

  • 构造出的字符串一定分成两部分,前一部分先放 A 在即将不符合限制时用 B 隔断(长度达不到 \(len\) 的也归到前一部分),后一部分相反。具体地,前一部分 \(i \bmod (len+1)=0\) 时填 B ,否则填 A ;后一部分 \((a+b-i+1) \bmod (len+1)=0\) 时填 A ,否则填 B

  • 考虑二分答案去找到这个分界点 \(mid\) 满足左边有 \(l_{A}=\left\lfloor \frac{mid}{len+1} \right\rfloor len+mid \bmod (len+1)\)A\(l_{B}=\left\lfloor \frac{mid}{len+1} \right\rfloor\)B ,通过 \(r_{A}<\left\lceil \frac{r_{B}}{len} \right\rceil\) 来判断是否合法。

    点击查看代码
    bool check(ll mid,ll a,ll b,ll len)
    {
    	return (a-mid/(len+1)*len-mid%(len+1))*len<(b-mid/(len+1));
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	ll t,a,b,c,d,l,r,ans,mid,len,i,j;
    	cin>>t;
    	for(j=1;j<=t;j++)
    	{
    		cin>>a>>b>>c>>d;
    		len=ceil(1.0*max(a,b)/(min(a,b)+1));
    		l=0;
    		ans=r=a+b;
    		while(l<=r)
    		{
    			mid=(l+r)/2;
    			if(check(mid,a,b,len)==true)
    			{
    				ans=mid;
    				r=mid-1;
    			}
    			else
    			{
    				l=mid+1;
    			}
    		}
    		for(i=c;i<=d;i++)
    		{
    			if(i<=ans)  cout<<((i%(len+1)==0)?'B':'A');
    			else  cout<<(((a+b-i+1)%(len+1)==0)?'A':'B');
    		}
    		cout<<endl;
    	}
    	return 0;
    }
    

\(M\) luogu P7216 [JOISC2020] 美味しい美味しいハンバーグ

\(N\) CF538G Berserk Robot

posted @ 2025-01-14 21:29  hzoi_Shadow  阅读(275)  评论(0)    收藏  举报
扩大
缩小