【学习笔记】AGC061

文章探讨了在处理序列交换操作时,如何利用位运算的性质,特别是异或和左移操作,来归纳出偶数长度序列的交换规律。对于奇数长度的情况也进行了分析。此外,文章还涉及了一类神仙构造题,通过实例展示了如何构建特定的矩阵布局,满足特定条件。整个过程展现了数学和算法的巧妙应用。
摘要由CSDN通过智能技术生成

A - Long Shuffle

n n n是偶数时,我们发现只会在 A 2 i − 1 A_{2i-1} A2i1 A 2 i A_{2i} A2i之间交换,对于 n n n更大的情况似乎没有什么明显的规律。

让我们尝试归纳一下。设 n n n为偶数,那么操作 ( 1 , n ) (1,n) (1,n)相当于 ( 1 , n − 2 ) + ( 3 , n ) (1,n-2)+(3,n) (1,n2)+(3,n)(这里的加法表示操作拼接的意思),接下来的尝试非常脑洞:如果 A 2 i − 1 A_{2i-1} A2i1 A 2 i A_{2i} A2i交换那么这一位是 1 1 1,我们可以把 n n n次操作后的结果看成一个二进制数 x x x,那么两个操作的拼接就等价于每一个位置上的异或,显然我们有结论: x ′ = x ⊕ ( x < < 1 ) x'=x\oplus (x<<1) x=x(x<<1)。这里有意思的地方就在于位运算操作具有分配律,我们把左移操作放进每个异或里面去,然后观察展开后 ( x < < i ) (x<<i) (x<<i)前面的系数,发现 其恰好符合杨辉三角的规律 ,于是可以归纳得出结论: A 2 i − 1 A_{2i-1} A2i1 A 2 i A_{2i} A2i交换当且仅当 ( n / 2 − 1 i − 1 ) \binom{n/2-1}{i-1} (i1n/21)为奇数。

对于 n n n为奇数的情况,显然我们可以看成 ( 1 , n − 1 ) + ( 2 , n ) (1,n-1)+(2,n) (1,n1)+(2,n),只需细致的讨论即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int T;
ll n,K;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>K,K--;
		if(n%2==0){
			if(((n/2-1)&(K/2))==K/2)K^=1;
			cout<<K+1<<"\n";
		}
		else{
			if(K){
				K--;
				if(((n/2-1)&(K/2))==K/2)K^=1;
				K++;	
			}
			if(((n/2-1)&(K/2))==K/2)K^=1;
			cout<<K+1<<"\n";
		}
	}
}

B - Summation By Construction

神仙构造题。

题解告诉我们,可以从邻接矩阵的角度考虑。手玩 n = 3 , 5 n=3,5 n=3,5的情况,可以发现每个颜色必须在每一行恰好出现两次或在每一列恰好出现两次,经过不懈的尝试 题解的提示 ,我们可以得到 n = 5 n=5 n=5时的方案:

5 5 3 3 1 1
4 5 5 3 3 4
4 4 5 5 3 3
2 4 4 5 5 2
2 2 4 4 5 5

不难验证这个构造是正确的。

然后考虑 n n n为偶数的情况。因为这些情况的构造都是等价的,因此只用考虑 n = 6 n=6 n=6的情形。

接下来的想法非常脑洞。我们考虑在 5 × 5 5\times 5 5×5的基础上扩展

6 6 4 4 2 . .
5 6 6 4 4 . .
5 5 6 6 4 . .
3 5 5 6 6 . .
3 3 5 5 6 . .
. . . . . . .

注意每种颜色扩展的长度为 3 3 3,以左下角的颜色为例,应该是向下扩展 1 1 1格,再向右扩展 2 2 2格。总之最后结果如下:

6 6 4 4 2 5 2
5 6 6 4 4 5 2
5 5 6 6 4 4 3
3 5 5 6 6 4 3
3 3 5 5 6 6 1
6 3 4 5 2 6 1

注意此时颜色 n n n形成了一个环,这可以通过只改变 1 , 2 , n 1,2,n 1,2,n的路径来解决:

6 6 4 4 2 5 2
5 6 6 4 4 5 1
5 5 6 6 4 4 3
3 5 5 6 6 4 3
3 3 5 5 6 6 1
2 3 4 5 2 6 6
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int T,n,a[105][105],in[205];
void work(int n){
	for(int i=0;i<n;i++){
		a[i][i]=a[i][i+1]=n;
	}
	for(int i=1;i<=n/2;i++){
		int s=n-2*i+1; 
		for(int j=0;j<s;j++){
			a[2*i-1+j][(n+j)%(n+1)]=a[2*i-1+j][j]=s;
		}
	}
	for(int i=1;i<=n/2;i++){
		int s=n-2*i;
		for(int j=0;j<s;j++){
			a[j][2*i+j]=a[j][2*i+j+1]=s;
		}
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--){
		cin>>n;
		if(n==2){
			cout<<"No"<<"\n";
			continue;
		}cout<<"Yes"<<"\n";
		if(n&1){
			for(int i=0;i<n;i++){
				a[i][i]=a[i][i+1]=n;
			}
			for(int i=1;i<=n/2;i++){
				int s=n-2*i+1; 
				for(int j=0;j<s;j++){
					a[2*i-1+j][(n+j)%(n+1)]=a[2*i-1+j][j]=s;
				}
			}
			for(int i=1;i<=n/2;i++){
				int s=n-2*i;
				for(int j=0;j<s;j++){
					a[j][2*i+j]=a[j][2*i+j+1]=s;
				}
			}
			for(int i=0;i<n;i++){
				for(int j=0;j<n+1;j++){
					cout<<a[i][j]<<' ';
				}cout<<"\n";
			}
		}
		else{
			work(n-1);
			for(int i=0;i<n;i++){
				for(int j=0;j<n+1;j++){
					if(i<n-1&&j<n-1)a[i][j]++;
					else a[i][j]=0;
				}
			}
			for(int i=1;i<n/2;i++){
				int s=n-2*i+1;
				a[n-1][s-2]=a[2*i-1][n]=a[2*i-2][n]=s;
			}
			for(int i=1;i<n/2;i++){
				int s=n-2*i;
				a[n-1][2*i]=a[n-2*i-1][n-1]=a[n-2*i-2][n-1]=s;
			}
			swap(a[0][n-1],a[0][n]),swap(a[1][n-1],a[1][n]);
			a[n-2][n-1]=a[n-1][n-1]=a[n-1][0]=n;
			a[n-2][n]=a[n-1][n]=1;a[0][n]=a[1][n]=2;
			a[1][n]=1,a[n-1][0]=2,a[n-1][n]=n;
			for(int i=0;i<n;i++){
				for(int j=0;j<n+1;j++){
					cout<<a[i][j]<<' ';
				}cout<<"\n"; 
			}
		}
	}
}

C - First Come First Serve

C i = 0 / 1 C_i=0/1 Ci=0/1表示选的是 A i / B i A_i/B_i Ai/Bi,问题相当于数本质不同的 { C i } \{C_i\} {Ci}数目,这里的本质不同指的是生成的排列 P P P不同。

然后对于小数据手玩一下,可以得出一个看着很对的结论:若 C 1 C_1 C1, C 2 C_2 C2是本质相同的,那么 min ⁡ ( C 1 , C 2 ) \min(C_1,C_2) min(C1,C2)也是相同的。这里的 min ⁡ \min min就是按位取最小值的意思。所以我们对于一个等价类取最小的那个作为代表元。

我们可以尝试来数 { C i } \{C_i\} {Ci}。显然 C i = 0 C_i=0 Ci=0的位置都是好的,对于 C i = 1 C_i=1 Ci=1的位置,显然 A i A_i Ai比之前的 C j = 0 C_j=0 Cj=0的位置都高,并且要相对顺序发生变化的话,就要存在一个 C j = 1 ( j < i ) C_j=1(j<i) Cj=1(j<i)使得 B j > A i B_j>A_i Bj>Ai,或者存在一个 C j = 0 ( j > i ) C_j=0(j>i) Cj=0(j>i)使得 A j < B i A_j<B_i Aj<Bi

d p dp dp部分有点复杂。让我们先冷静一下,设 r i r_i ri表示最大的满足 A j < B i A_j<B_i Aj<Bi的点,那么 i i i对后面的限制相当于 [ i : r i ] [i:r_i] [i:ri]中必须有一个为 0 0 0,那么取 r i r_i ri最小的那个一定就是最紧的限制,又因为 r i r_i ri是单增的,所以我们似乎要维护第一个对后面有限制的那个位置,这样就没办法优化了。

倒着来会不会好一点?

正解非常脑洞。我们考虑 i i i不合法时的情况 ,设 l j l_j lj表示 B j > A i B_j>A_i Bj>Ai的最小的点,那么 [ l j : i ) [l_j:i) [lj:i)都必须全为 0 0 0;设 r j r_j rj表示 A j < B i A_j<B_i Aj<Bi的最大的点,那么 ( i : r j ] (i:r_j] (i:rj]都必须全为 1 1 1。然后我们容斥不合法的那些位置,如果 [ l i : r i ] ∩ [ l j : r j ] ≠ ∅ [l_i:r_i]\cap [l_j:r_j]\ne \varnothing [li:ri][lj:rj]=那么方案数为 0 0 0 ,因此被固定的位置数目就是所有限制区间长度之和,设 f i f_i fi表示考虑前 i i i个位置时的容斥系数之和,如果 i i i有限制那么 f i = ∑ R j < L i f j 2 L i − R i − 1 ( − 1 ) f_i=\sum_{R_j<L_i}f_j2^{L_i-R_i-1}(-1) fi=Rj<Lifj2LiRi1(1),用树状数组维护会多一个 log ⁡ \log log,如果按 R i R_i Ri排序再 d p dp dp就能做到 O ( n ) O(n) O(n)了。

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define db double
using namespace std;
const int mod=998244353;
const int N=5e5+5;
int n,a[N],b[N],L[N],R[N]; 
ll bit[N],f[N],pw[N],invpw[N],inv2=(mod+1)/2,sum=1;
int s[N],cnt;
void upd(ll x,ll y){
	for(;x<=n;x+=x&-x)bit[x]=(bit[x]+y)%mod;
}
ll ask(ll x){
	ll tot(0);
	for(;x;x-=x&-x)tot=(tot+bit[x])%mod;
	return tot;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;pw[0]=invpw[0]=1;for(int i=1;i<=n;i++)pw[i]=pw[i-1]*2%mod,invpw[i]=invpw[i-1]*inv2%mod;
	for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
	for(int i=1;i<=n;i++){
		int l=1,r=i-1,res=i;
		while(l<=r){
			int mid=l+r>>1;
			if(b[mid]>a[i])res=mid,r=mid-1;
			else l=mid+1;
		}L[i]=res;
		l=i+1,r=n,res=i;
		while(l<=r){
			int mid=l+r>>1;
			if(a[mid]<b[i])res=mid,l=mid+1;
			else r=mid-1;
		}R[i]=res;
		f[i]=-(ask(L[i]-1)+1)*invpw[R[i]-L[i]+1]%mod;
		upd(R[i],f[i]);
		sum=(sum+f[i])%mod;
	}sum=sum*pw[n]%mod;
	cout<<(sum+mod)%mod;
}

D - Almost Multiplication Table

感觉题解的做法挺有道理的,但是不太清楚这样为什么一定是对的。不过 { X i , Y i } \{X_i,Y_i\} {Xi,Yi}为正整数的条件可以利用一下,可能要证明一下它是收敛的吧。

E - Increment or XOR

点这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值