Atcoder Beginner Contest 380 (D~G)

D.Strange Mirroring

题意:
给定一个只含有大小写字母的字符串 $ S $。
现在对这个字符串操作无数次:

  • 对于 $ S $ 的每个字符,若是大写字母就改为对应的小写字母,否则改成对应的大写字母,形成一个新的字符串 $ T $。
  • 将 $ S $ 和 $ T $ 首尾连接,形成新的 $ S $。

现在给定 $ Q $ 次询问 $ K_i $,表示询问完成上述操作后 $ S $ 的第 $ K_i $ 个字符。

题解:
下面所有的序号从0开始
结论:
对于第 $ i $ 个字符串段,若 $ bitcount(i) $ 为奇数,那么是逆反字符串;反之为正常字符串。
简单证明(?):
我们发现每一次操作实际上是所有的序号在二进制下向高位拓展了一位,前半序号这一位为 $ 0 $ ,后一半这一位为 $ 1 $ 。我们发现这实际上是 $ bitcount(i) $取2的模。

所以只需要找到 $ K_i $ 所处在的字符串段,然后根据上述规律用__builtin_popcountll()即可得到答案(一定要用popcountll)。

关于 $ bitcount(i) $对2取模后的序列,其实是Thue-Morse序列,这里插个链接供以后学习:

https://zhuanlan.zhihu.com/p/385807077

代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	string s;
	cin>>s;
	int q;
	cin>>q;
	int len=s.length();
	string t=s;
	for(int i=0;i<s.length();i++)
	{
		if(s[i]>='a'&&s[i]<='z') t[i]=(char)(s[i]-'a'+'A');
		else if(s[i]>='A'&&s[i]<='Z')t[i]=(char)(s[i]-'A'+'a');
	}
	s=' '+s;
	t=' '+t;
	while(q--)
	{
		int k;
		cin>>k;
		int which;
		if(k%len==0) which=k/len;
		else which=k/len+1;
		which--;
		int pos=k%len;
		if(pos==0) pos=len;
		int much=__builtin_popcountll(which);
        much%=2;
		if(!much) cout<<s[pos]<<' ';
		else cout<<t[pos]<<' ';
	}
	
	
	
	return 0;
 } 

E.1D Bucket Tool

题意:
给出编号从 $ 1 $ 到 $ n $ 的 $ n $ 个格子,初始第 $ i $ 个格子的颜色为 $ i $ ,接着需要你维护两种操作,第一种操作是将 $ x $ 所在的色块 (与第 $ x $ 个格子颜色相同且包含第 $ x $ 个格子的最大区间)全涂上颜色 $ c $ ,第二种操作是输出颜色为 $ c $ 的格子数量。

题解:
考虑并查集,我们只需要维护对于某一个并查集,其 $ father $ 的颜色 ,该并查集的大小,以及该并查集(实际上就是一个色块)的最左侧与最右侧的下标即可。每一次更改颜色时,只需要改变 $ father $ 的颜色,此外,由于更改颜色可能会导致并查集的扩大,这个时候就要和左侧的相邻色块以及右侧的相邻色块进行讨论与合并。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int maxn=5e5+10;
int fa[maxn],sz[maxn];
int num[maxn],col[maxn];
int Left[maxn],Right[maxn];

int find(int x)
{
	return fa[x]==x?x:fa[x]=find(fa[x]);
}

void hb(int x,int y)
{
	int fa1=find(x);
	int fa2=find(y);
	if(fa1==fa2) return ;
	fa[fa2]=fa1;
	sz[fa1]+=sz[fa2]; 
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int n,q;
	cin>>n>>q;
	for(int i=1;i<=n;i++)
	{
		fa[i]=col[i]=i,sz[i]=num[i]=1;
		Left[fa[i]]=i;
		Right[fa[i]]=i;
	}
	while(q--)
	{
		int type;
		cin>>type;
		if(type==1)
		{
			int x,c;
			cin>>x>>c;
			int father=find(x);
			int color=col[father];
			num[color]-=sz[father];
			num[c]+=sz[father];
			col[father]=c;
			if(Left[father]-1>=1&&col[find(Left[father]-1)]==c)
			{
				int new_left=Left[find(Left[father]-1)];
				hb(father,Left[father]-1);
				Left[father]=new_left;
			}
			if(Right[father]+1<=n&&col[find(Right[father]+1)]==c)
			{
				int new_right=Right[find(Right[father]+1)];
				hb(father,Right[father]+1);
				Right[father]=new_right;
			}
		}
		else 
		{
			int c;
			cin>>c;
			cout<<num[c]<<endl;
		}
	}
	
	return 0;
 } 

F.Exchange Game

题意:
$ Takahashi $ 和 $ Aoki $ 在玩一个卡牌游戏,每张卡牌上写有一个数字。
初始时 $ Takahashi $ 有 $ N $ 张牌, $ Aoki $ 有 $ M $ 张牌,桌上还有 $ L $ 张牌。游戏规则如下:

  • 轮到某个玩家的回合时,该玩家从手中打出一张牌(记为 $ K $ )放置在桌子上,并允许在桌子上拿不超过一张小于 $ K $ 牌。
  • 当一个玩家不能进行上述操作时,对方获胜,游戏结束。

若 $ Takahashi $ 先手,双方都知道所有牌的布局,问最优策略下谁是必胜者。

$ N + M + L ≤ 12 $

题解:
令 $ S = N + M + L $ 观察到 $ S $ 是不超过 $ 12 $ 的。那么直接三进制状压 $ dp $。设 $ dp[s][1/2] $ 为,当前牌的分布状态为 $ s $ ,当前是 $ 1/2 $ 号玩家作为先手是否存在必胜策略,直接进行转移就行了,搜索末态就是当一位选手手中无牌的时候,该玩家必输。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int maxn=1600000;
int mi[20],val[20];
int n,m,l;
int dp[maxn][3];//dp[i][1/2]代表状态为i时,1/2先手是否会win

int dfs(int s,int x)
{
	if(dp[s][x]) return dp[s][x];
	vector<int>mine,all;
	for(int i=0;i<n+m+l;i++)
	{
		int which=s/mi[i]%3;
		if(which==x) mine.push_back(i);
		if(which==0) all.push_back(i);
	}
	if(mine.size()==0)
	{
		if(x==1) dp[s][x]=-1;
		else dp[s][x]=1;
		return dp[s][x];
	}
	for(auto it1:mine)
	{
		int s_new=s-x*mi[it1];
		dp[s_new][(x==1?2:1)]=dfs(s_new,(x==1?2:1));
		if((dp[s_new][x==1?2:1]==1&&x==1)||(dp[s_new][x==1?2:1]==-1&&x==2)) return dp[s][x]=dp[s_new][x==1?2:1];
		for(auto it2:all)
		{
			if(val[it1]>val[it2])
			{
				int new_s=s;
				new_s-=x*mi[it1];
				new_s+=x*mi[it2];
				dp[new_s][(x==1?2:1)]=dfs(new_s,(x==1?2:1));
				if((dp[new_s][x==1?2:1]==1&&x==1)||(dp[new_s][x==1?2:1]==-1&&x==2)) return dp[s][x]=dp[new_s][x==1?2:1];
			}
		}
	}
	return dp[s][x]=(x==1?-1:1);
} 

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	mi[0]=1;
	for(int i=1;i<=15;i++) mi[i]=mi[i-1]*3;
	cin>>n>>m>>l;
	for(int i=0;i<n+m+l;i++) cin>>val[i];
	int s0=0;
	for(int i=0;i<n;i++) s0+=mi[i]*1;
	for(int i=n;i<n+m;i++) s0+=mi[i]*2;
	dfs(s0,1);
	if(dp[s0][1]==1) cout<<"Takahashi"<<endl;
	else if(dp[s0][1]==-1) cout<<"Aoki"<<endl;
	
	return 0;
}

G.Another Shuffle Window
题意:
给定一个排列 $ P=(1,2...,N)$ 和一个整数 $ K $。
请计算经过以下操作后,排列 $ P $的逆序对数的期望值:

  • 首先,从 $ 1 $ 到 $ N-K+1 $ 的整数中随机均匀地选择一个整数 $ i $;
  • 然后,将子数组 $ p_i,p_{i+1},...,p_{i+k-1} $进行随机均匀打乱。

题解:
先给出结论,对于长度为 $ N $ 的排列,将其均匀打乱后产生的逆序对期望为 $ N(N+1)/4 $ 。
证明:
对于长度为 $ N $ 的排序,一共可以产生 $ N(N+1)/2 $ 对二元关系,而每对二元关系产生逆序对的期望为 $ 0.5 $ ,那么对于长度为 $ N $ 的排列,将其均匀打乱后产生的逆序对期望为 $ N(N+1)/4 $。
那么我们现在只需要求出长度为 $ N $ 的数组中的 $ N-K+1 $ 个长度为 $ K $ 的子数组中各自的逆序对为多少即可。我们先预处理出 $ P_1 $ 到 $ P_K $ 中产生的逆序对的个数,我们在向右平移的过程中,每次只需要减去 $ P_i $ 的逆序对,加上 $ p_{i+k} $ 产生的逆序对就可以得到新的逆序对总和了。最后将这 $ N-K+1 $ 个总和累加起来除以 $ N-K+1 $ ,最后加上 $ N(N+1)/4 $ 就可以得到正确结果了。

代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

const int mod=998244353;
const int maxn=2e5+10;
int tree[maxn],p[maxn],n,k;

int fast_pow(int a,int n,int mod)
{
	int ans=1;
	a%=mod;
	while(n)
	{
		if(n&1) ans=(ans*a)%mod;
		a=(a*a)%mod;
		n>>=1;
	}
	return ans;
}

int inv(int a,int mod)
{
	return fast_pow(a,mod-2,mod);
}

int lowbit(int x)
{
	return x&(-x);
}

void add(int x,int k)
{
	while(x<=n)
	{
		tree[x]+=k;
		x+=lowbit(x);
	}
}

int query(int x)
{
	int ans=0;
	while(x)
	{
		ans+=tree[x];
		x-=lowbit(x);
	}
	return ans;
}


signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>p[i];
	int sum=0;
	for(int i=n;i>=1;i--)
	{
		sum+=query(p[i]);
		add(p[i],1);
	}
	memset(tree,0,sizeof(tree));
	int now=0;
	for(int i=k;i>=1;i--)
	{
		now+=query(p[i]);
		add(p[i],1);
	}
	int ans=sum-now;
	for(int i=1;i<=n-k;i++)
	{
		add(p[i],-1);
		now-=query(p[i]);
		now+=query(n)-query(p[i+k]);
		add(p[i+k],1);
		ans=(ans+sum-now)%mod;
	}
	cout<<(ans*inv(n-k+1,mod)+k*(k-1)%mod*inv(4,mod))%mod<<endl;
	
	return 0;
}
posted on 2024-12-09 09:35  Linear_L  阅读(26)  评论(1)    收藏  举报