AtCoder Beginner Contest 380 赛后总结
菜菜菜,不是你怎么这么菜。
A-C
模拟即可。
D
正常的方法
因为不管怎么粘合总是一个字符串在复制,所以我们只用考虑大小写问题。
我们设字符串为 \(A\),被反转大小写的字符串为 \(B\),那么这个字符串会长这样:\(ABBABAABBAABABBA\cdots\),第一个 \(A\) 的位置是 \(0\) 的话,我们可以发现每一个 \(B\) 在二进制中的 \(1\) 的个数为奇数。
证明
我们知道,每一次反转字符串长度会乘 \(2\)。
所以字符串中的每一位象征着是经过多少次反转过后得到的。
所以每个字符串的位置在二进制下的 \(1\) 的个数就是反转大小写的次数,
那么一定是反转奇数次才可以变换样式。
幽默方法
按照上面的方法先找出字符串的样子,发现被反转后的字符串的位置在 \(\{1,2,4,7,8,11,\cdots\}\),丢到 oeis 中找到 A000069,发现规律是二进制中 \(1\) 的个数。
点击查看代码
#include<bits/stdc++.h>
#define int ll
#define mem(a,b) memset((a),(b),sizeof(a))
#define m0(a) memset((a),0,sizeof(a))
#define lb(x) ((x)&-(x))
#define lc(x) ((x)<<1)
#define rc(x) (((x)<<1)|1)
#define pb(G,x) (G).push_back((x))
#define For(a,b,c) for(int a=(b);a<=(c);a++)
#define Rep(a,b,c) for(int a=(b);a>=(c);a--)
#define in1(a) a=read()
#define in2(a,b) a=read(), b=read()
#define in3(a,b,c) a=read(), b=read(), c=read()
#define inn(i,n,a) For(i,1,n) a[i]=read();
#define ll long long
#define i128 __int128
using namespace std;
inline int read() {
int xx= 0;int f= 1;
char c = getchar();
while(c<'0'||c>'9') {
if(c=='-') f= -1;
c= getchar();
}
while(c>='0'&&c<='9') {
xx= (xx<<1)+(xx<<3)+(c^48);
c= getchar();
}
return xx*f;
}
#define maxn 200050
string s;
int q;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin>>s>>q;
int n=s.size();
while(q--) {
int x; cin>>x;
int a=(x-1)/n;
int cnt=0; //cout<<a<<':';
while(a) cnt+=(a&1),a>>=1;
if(!(cnt&1)) cout<<s[(x-1)%n]<<' ';
else {
int i=(x-1)%n;
if(s[i]>='a') cout<<(char)(s[i]-32)<<' ';
else cout<<(char)(s[i]+32)<<' ';
}
}
}
E
并查集基础练习题,我们记录连通块的左/右端点 \(l,r\),父亲节点对应的颜色 \(col\),颜色的个数 \(siz\),那么每一次合并只用考虑区间左/右侧一个格子是否和现在的格子颜色相同,维护 \(l,r\) 即可。合并时要注意使用的变量会不会因为更改导致前后表达的意思不同。
点击查看代码
#include<bits/stdc++.h>
#define mem(a,b) memset((a),(b),sizeof(a))
#define m0(a) memset((a),0,sizeof(a))
#define lb(x) ((x)&-(x))
#define lc(x) ((x)<<1)
#define rc(x) (((x)<<1)|1)
#define pb(G,x) (G).push_back((x))
#define For(a,b,c) for(int a=(b);a<=(c);a++)
#define Rep(a,b,c) for(int a=(b);a>=(c);a--)
#define in1(a) a=read()
#define in2(a,b) a=read(), b=read()
#define in3(a,b,c) a=read(), b=read(), c=read()
#define inn(i,n,a) For(i,1,n) a[i]=read();
#define ll long long
#define i128 __int128
using namespace std;
inline int read() {
int xx= 0;int f= 1;
char c = getchar();
while(c<'0'||c>'9') {
if(c=='-') f= -1;
c= getchar();
}
while(c>='0'&&c<='9') {
xx= (xx<<1)+(xx<<3)+(c^48);
c= getchar();
}
return xx*f;
}
#define maxn 500050
int fa[maxn];
int col[maxn];
int l[maxn],r[maxn],siz[maxn];
int n,q;
int find(int x) {return x==fa[x]?x:fa[x]=find(fa[x]); }
signed main() {
in2(n,q);
For(i,1,n) col[i]=i,fa[i]=i,l[i]=i,r[i]=i,siz[i]=1;
while(q--) {
int opt=read();
if(opt==1) {
int x=read(),c=read();
int X=find(x);
if(col[X]==c) continue;
siz[col[X]]-=r[X]-l[X]+1;
col[X]=c;
siz[col[X]]+=r[X]-l[X]+1;
if(l[X]>1) {
int L=find(l[X]-1);
if(col[L]==c) {
l[X]=l[L];
fa[L]=X;
}
}
if(r[X]<n) {
int R=find(r[X]+1);
if(col[R]==c) {
r[X]=r[R];
fa[R]=X;
}
}
} else {
int c=read();
cout<<siz[c]<<'\n';
}
}
}
F
\(n+m+l\le 12\),一眼搜索剪枝。
如果有一种操作使得之后的情况都可以转换为我胜,那么这就是我的必胜点,反之就是我的必败点。
注意是可以从桌上拿起一张牌,不是必须。
然后就是要记忆化搜索,注意要记录此时是谁操作,因为有可能出现两个人手牌一样但是操作方不一样。
点击查看代码
#include<bits/stdc++.h>
#define mem(a,b) memset((a),(b),sizeof(a))
#define m0(a) memset((a),0,sizeof(a))
#define lb(x) ((x)&-(x))
#define lc(x) ((x)<<1)
#define rc(x) (((x)<<1)|1)
#define pb(G,x) (G).push_back((x))
#define For(a,b,c) for(int a=(b);a<=(c);a++)
#define Rep(a,b,c) for(int a=(b);a>=(c);a--)
#define in1(a) a=read()
#define in2(a,b) a=read(), b=read()
#define in3(a,b,c) a=read(), b=read(), c=read()
#define inn(i,n,a) For(i,1,n) a[i]=read();
#define ll long long
#define i128 __int128
using namespace std;
inline int read() {
int xx= 0;int f= 1;
char c = getchar();
while(c<'0'||c>'9') {
if(c=='-') f= -1;
c= getchar();
}
while(c>='0'&&c<='9') {
xx= (xx<<1)+(xx<<3)+(c^48);
c= getchar();
}
return xx*f;
}
#define maxn 15
short f[2][(1<<12)+114][(1<<12)+514];
int a[maxn];
int n,m,l;
int out[maxn],top;
void put(int x) {
m0(out);
top=0;
while(x) out[++top]=x&1,x>>=1;
For(i,1,13) cout<<out[i];
puts("");
}
bool dfs(int dep,int s1,int s2) {
// cout<<dep<<'\n';put(s1); put(s2); puts("---");
bool res=0;
if(f[dep][s1][s2]!=-1) return f[dep][s1][s2];
if(dep&1) {
res=0;
For(i,0,n+m+l-1) {
if(!((s1>>i)&1)) continue;
if((s2>>i)&1) continue;
For(j,0,n+m+l-1) {
if(((s1|s2)>>j)&1) continue;
if(a[i+1]>a[j+1])
if(!dfs((dep^1),(s1^(1<<i))|(1<<j),s2)) res=1;
}
if(!dfs((dep^1),(s1^(1<<i)),s2)) res=1;
}
} else {
res=0;
For(i,0,n+m+l-1) {
if(!((s2>>i)&1)) continue;
if((s1>>i)&1) continue;
For(j,0,n+m+l-1) {
if(((s1|s2)>>j)&1) continue;
if(a[i+1]>a[j+1])
if(!dfs((dep^1),s1,(s2^(1<<i))|(1<<j))) res=1;
}
if(!dfs((dep^1),s1,(s2^(1<<i)))) res=1;
}
}
f[dep][s1][s2]=res;
return res;
}
signed main() {
mem(f,-1);
in3(n,m,l);
For(i,1,n+m+l) in1(a[i]);
cout<<(dfs(1,(1<<(n))-1,(1<<(m+n))-(1<<n))?"Takahashi":"Aoki");
}
G
好题啊!
我们枚举区间左端点 \(i\),发现整个序列被分为了 \(3\) 个部分,左边部分 \(A\),中间部分 \(B\),右边部分 \(C\)。
我们讨论一下每个部分对答案的贡献:
- \(AA\)/\(CC\)/\(AC\),因为数字是固定的,所以这三部分的答案也是固定的。
- \(AB\)/\(BC\),由于个个逆序对不相互影响,所以这两部分答案也是固定的。
- \(BB\),有 \(\frac{k\times(k-1)}{2}\) 种方法选数,对于每一对数构成逆序对的概率都是 \(\frac{1}{2}\),故这部分答案为 \(\frac{k\times(k-1)}{4}\)。
我们可以用树状数组求逆序对的方法来维护各个部分的值: - 在 \(A\) 的右边加入一个值,那么会答案会更改成:
- \(AA\):由于在右侧加入,想要构成逆序对必须要和前面的比这个数大的数进行匹配;
- \(AB\)/\(AC\):由于在相对的左侧加入,所以要和比自己小的数进行匹配。
- 在 \(B\) 中加入一个值,那么答案会更改成:
- \(AB\):相对在右边,故和比自己大的数匹配;
- \(AC\):相对在左边,故和比自己小的数匹配;
- 在 \(C\) 的左侧加入一个值,那么答案会更改成:
- \(AC\)/\(BC\):相对在右边,故比自己大的数匹配;
- \(CC\):在左边,故和比自己小的数匹配。
删去一个值同理。
由于树状数组是 \(O(\log n)\) 的,故总时间复杂度为 \(O(n\log n)\)。
注意判断是否溢出,根据需要选择 __int128
或者 long long
。
点击查看代码
#include<bits/stdc++.h>
#define int i128
#define mem(a,b) memset((a),(b),sizeof(a))
#define m0(a) memset((a),0,sizeof(a))
#define lb(x) ((x)&-(x))
#define lc(x) ((x)<<1)
#define rc(x) (((x)<<1)|1)
#define pb(G,x) (G).push_back((x))
#define For(a,b,c) for(int a=(b);a<=(c);a++)
#define Rep(a,b,c) for(int a=(b);a>=(c);a--)
#define in1(a) a=read()
#define in2(a,b) a=read(), b=read()
#define in3(a,b,c) a=read(), b=read(), c=read()
#define inn(i,n,a) For(i,1,n) a[i]=read();
#define ll long long
#define i128 __int128
using namespace std;
inline int read() {
int xx= 0;int f= 1;
char c = getchar();
while(c<'0'||c>'9') {
if(c=='-') f= -1;
c= getchar();
}
while(c>='0'&&c<='9') {
xx= (xx<<1)+(xx<<3)+(c^48);
c= getchar();
}
return xx*f;
}
#define maxn 200050
const int mod = 998244353;
int c[4][maxn];
/*
1:A
2:B
3:C
*/
int ans[6];
/*
1:AA
2:AB
3:AC
4:BC
5:CC
*/
int n,k;
int a[maxn];
void add(int idx,int x,int val) {
for(;x<maxn;x+=lb(x)) c[idx][x]+=val;
}
int query(int idx,int x) {
int res=0;
for(;x;x-=lb(x)) res+=c[idx][x];
return res;
}
int re_cnt(int idx,int x,bool flg) {
if(!flg) return query(idx,x-1);
else return query(idx,n)-query(idx,x);
}
void upd_a(int x,int val) {
add(1,x,val);
ans[1]+=re_cnt(1,x,1)*val;
ans[2]+=re_cnt(2,x,0)*val;
ans[3]+=re_cnt(3,x,0)*val;
}
void upd_b(int x,int val) {
add(2,x,val);
ans[2]+=re_cnt(1,x,1)*val;
ans[4]+=re_cnt(3,x,0)*val;
}
void upd_c(int x,int val) {
add(3,x,val);
ans[3]+=re_cnt(1,x,1)*val;
ans[4]+=re_cnt(2,x,1)*val;
ans[5]+=re_cnt(3,x,0)*val;
}
int qpo(int a,int b) {
int res=1;
while(b) {
if(b&1) res=(res*a)%mod;
a*=a;
a%=mod;
b>>=1;
}
return res;
}
int inv(int x) {return qpo(x,mod-2); }
signed main() {
in2(n,k);
For(i,1,n) in1(a[i]);
For(i,1,k) upd_b(a[i],1);
Rep(i,n,k+1) upd_c(a[i],1);
int res=(ans[1]+ans[2]+ans[3]+ans[4]+ans[5]+(k*(k-1))*inv(4))%mod;
For(i,1,n-k) {
upd_b(a[i],-1);
upd_a(a[i],1);
upd_c(a[i+k],-1);
upd_b(a[i+k],1);
res=(res+ans[1]+ans[2]+ans[3]+ans[4]+ans[5]+(k*(k-1))*inv(4))%mod;
}
cout<<(ll)((res*inv(n-k+1))%mod);
}
本文来自博客园,作者:coding_goat_qwq,转载请注明原文链接:https://www.cnblogs.com/CodingGoat/p/18550780