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;
}