Codeforces Round #779 Div.2
A.Marin and Photoshoot
题意:有一串01串,0代表男生,1代表女生,你可以进行添加男生操作,使得对于任意一段
[
l
,
r
]
[l,r]
[l,r](r>=l+1)中男生的数量不超过女生。问最少操作几次。
题解:首先对于男生来说,两边都必须为女生,对于长度为3的时候,女生数量必须大于等于2。也就是说两个男生之间必须要有至少两个女生。
void solve(){
int n;
cin>>n;
string s;
cin>>s;
int a=0,b=0;
vector<int>v;
for(int i=0;i<n;i++){
if(s[i]=='0')v.push_back(i);
}
int ans=0;
for(int i=1;i<v.size();i++){
ans+=max(0,2-(v[i]-v[i-1]-1));
}
cout<<ans<<"\n";
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int t;
cin>>t;
// t=1;
while(t--){
solve();
}
return 0;
}
B.Marin and Anti-coprime Permutation
题意:
p
1
,
p
2
,
p
3
.
.
.
p
n
p_1,p_2,p_3...p_n
p1,p2,p3...pn为
[
1
,
n
]
[1,n]
[1,n]的排列,问满足
g
c
d
(
1
∗
p
1
,
2
∗
p
2
,
.
.
.
n
∗
p
n
)
>
1
gcd(1*p_1,2*p_2,...n*p_n)>1
gcd(1∗p1,2∗p2,...n∗pn)>1的方案数。
题解:
对于相邻的两个数,一奇一偶
g
c
d
gcd
gcd为
1
1
1,为了使得
g
c
d
>
1
gcd>1
gcd>1,我们必须把奇数安排在偶数位置,因此当n为奇数时,有一个奇数没有偶数位置安排,答案为0,n为偶数时,奇偶数分开全排列,
a
n
s
=
(
n
2
!
n
2
!
)
ans=(\frac{n}{2}!\frac{n}{2}!)
ans=(2n!2n!)
ll fac[maxn];
void solve(){
ll n;
cin>>n;
if(n%2)cout<<"0\n";
else cout<<fac[n/2]*fac[n/2]%MOD<<"\n";
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int t;
cin>>t;
// t=1;
fac[0]=1;
for(ll i=1;i<maxn;i++)fac[i]=fac[i-1]*i%MOD;
while(t--){
solve();
}
return 0;
}
C.Shinju and the Lost Permutation
题意:现在有一个
[
1
,
n
]
[1,n]
[1,n]的数组P,但是顺序忘记了,只知道它的C数组,问这个C数组是否合法。
定义B数组为
b
i
=
m
a
x
(
p
1
,
p
2
,
.
.
.
p
i
)
b_i=max(p_1,p_2,...p_i)
bi=max(p1,p2,...pi)
定义C数组为
c
i
=
p
循
环
右
移
(
i
−
1
)
位
得
到
的
b
数
组
去
重
后
的
个
数
c_i=p循环右移(i-1)位得到的b数组去重后的个数
ci=p循环右移(i−1)位得到的b数组去重后的个数
首先B数组一定是递增的。
当最大值
n
n
n在第一个时,
B
=
n
,
n
,
.
.
n
,
c
i
=
1
B={n,n,..n},c_i=1
B=n,n,..n,ci=1。当c等于1时说明当前位置最大值在第一个位置。
那么我们对c数组移动使得1在第一个位置,这样当我们循环右移时最大值后面的状态都不用考虑,因为取max都为
n
n
n。当当前最后一个数移动到第一位时,然后小于后面的数,他就会使得c+1,所以相邻的两项最多只会差1,并且后面的c都不可能为1.
int a[maxn];
void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
vector<int>v;
int index=-1;
for(int i=1;i<=n;i++){
if(a[i]==1){
index=i;
break;
}
}
if(index==-1){
cout<<"NO\n";
return;
}
for(int i=index;i<=n;i++)v.push_back(a[i]);
for(int i=1;i<index;i++)v.push_back(a[i]);
for(int i=1;i<n;i++){
if(v[i]>i+1||v[i]==1||v[i]>v[i-1]+1){
cout<<"NO\n";
return;
}
}
cout<<"YES\n";
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int t;
cin>>t;
// t=1;
while(t--){
solve();
}
return 0;
}
D1.388535 (Easy Version)
题意:
[
l
,
r
]
区
间
中
的
所
有
数
异
或
上
x
得
到
a
数
组
[l,r]区间中的所有数异或上x得到a数组
[l,r]区间中的所有数异或上x得到a数组,问
x
x
x的值。
(
l
=
=
0
)
(l==0)
(l==0)
题解:
对
[
l
,
r
]
[l,r]
[l,r]中的数进行二进制拆分,对
[
a
1
,
a
r
−
l
+
1
]
[a_1,a_{r-l+1}]
[a1,ar−l+1]也进行拆分,若拆分后第
i
i
i位的个数不相同,说明这一位进行了异或操作,使得个数翻转了,将这一位计入答案。
int num[30],num2[30];
void solve(){
int l,r;
cin>>l>>r;
for(int i=0;i<=17;i++)num[i]=0;
for(int i=0;i<=17;i++)num2[i]=0;
for(int i=1,x;i<=r-l+1;i++){
cin>>x;
for(int j=0;j<=17;j++){
if((x>>j)&1){
num[j]++;
}
}
}
for(int i=l;i<=r;i++){
for(int j=0;j<=17;j++){
if((i>>j)&1){
num2[j]++;
}
}
}
int ans=0;
for(int i=0;i<=17;i++){
if(num[i]==num2[i])continue;
else ans+=(1<<i);
}
cout<<ans<<"\n";
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int t;
cin>>t;
// t=1;
while(t--){
solve();
}
return 0;
}
D2.388535 (Hard Version)
题意:同D1,但是
(
l
>
=
0
)
(l>=0)
(l>=0)
题解:当
l
!
=
0
l!=0
l!=0时,上面的构造方法就存在问题。
首先我们得知道
[
l
,
r
]
[l,r]
[l,r]中得值,两两异或都不为0,因为任意两个值都不相等。那么所有值都异或上
x
x
x后,所有值也都不相等,因为异或后得两个值异或等价于
(
i
⨁
x
)
⨁
(
j
⨁
x
)
=
(
i
⨁
j
)
(i \bigoplus x) \bigoplus (j \bigoplus x)=(i \bigoplus j)
(i⨁x)⨁(j⨁x)=(i⨁j),所以异或后的任意两个值也不相等,那么我们枚举
x
x
x的时候,只需要计算a数组异或上
x
x
x的最小值为
l
l
l,最大值为
r
r
r就可以确定这个数组异或上
x
x
x得到的为
[
l
,
r
]
[l,r]
[l,r]
异或最大最小值用01字典树可以log的算出,枚举x的时间为n,总时间为nlog
int tol; //节点个数
ll val[32*maxn]; //点的值
int ch[32*maxn][2]; //边的值
void init()
{ //初始化
for(int i=0;i<=tol;i++)ch[i][0]=ch[i][1]=0;
tol=1;
}
void insert(ll x)
{ //往 01字典树中插入 x
int u=0;
for(int i=16;i>=0;i--)
{
int v=(x>>i)&1;
if(!ch[u][v])
{ //如果节点未被访问过
ch[tol][0]=ch[tol][1]=0; //将当前节点的边值初始化
val[tol]=0; //节点值为0,表示到此不是一个数
ch[u][v]=tol++; //边指向的节点编号
}
u=ch[u][v]; //下一节点
}
val[u]=x; //节点值为 x,即到此是一个数
}
ll query(ll x)
{ //查询所有数中和 x异或结果最大的数
int res=0;
int u=0;
for(int i=16;i>=0;i--)
{
int v=(x>>i)&1;
//利用贪心策略,优先寻找和当前位不同的数
if(ch[u][v^1]) u=ch[u][v^1],res|=(1<<i);
else u=ch[u][v];
}
return res ;//返回结果
}
ll query_min(ll x)
{ //查询所有数中和 x异或结果最小的数
int res=0;
int u=0;
for(int i=16;i>=0;i--)
{
int v=(x>>i)&1;
//利用贪心策略,优先寻找和当前位不同的数
if(ch[u][v]) u=ch[u][v];
else u=ch[u][v^1],res|=(1<<i);
}
return res;//返回结果
}
void solve(){
int l,r,x;
cin>>l>>r;
init();
for(int i=l;i<=r;i++){
cin>>x;
insert(x);
}
for(int i=l;i<=r;i++){
int ans=x^i;
int mx=query(ans);
int mn=query_min(ans);
if(mn==l&&mx==r){
cout<<ans<<"\n";
return;
}
}
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}
E.Gojou and Matrix Game
题意:两个人博弈,有一个n*n的棋盘,每个点都有不同的值,两个下的棋子之间的曼哈顿距离不得小于等于k,问对于每个点,若先手下这个点,谁获胜。,每人下
1
0
100
次
10^{100}次
10100次
题解:对于后手下的人,若在先手的k距离外,没有比k距离内的值大,就输了,因为先手的一直都比你大,你又进不去k的范围内。反之后手就赢了。
按点值从大到小枚举,最大的点值先手下必胜,再进行转移,对于所有的必胜点都得在目前点的k范围内,只有这样目前点才能为必胜点。
判断是否覆盖所有必胜点只需要维护上下左右四个端点值。
int u,d,l,r,k,n;
bool check(int i,int j){
return (abs(i-u)<=k&&abs(i-d)<=k&&abs(j-l)<=k&&abs(j-r)<=k);
}
int dp[2222][2222];
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin>>n>>k;
vector<array<int,5>>v;
for(int i=1;i<=n;i++){
for(int j=1,x;j<=n;j++){
cin>>x;
v.push_back({x,i,j,i+j,j-i});
}
}
sort(v.begin(),v.end(),greater<array<int,5>>());
u=v[0][3];d=v[0][3];l=v[0][4];r=v[0][4];
for(auto &it:v){
if(check(it[3],it[4])){
dp[it[1]][it[2]]=1;
u=min(u,it[3]);
d=max(d,it[3]);
l=min(l,it[4]);
r=max(r,it[4]);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(dp[i][j])cout<<"M";
else cout<<"G";
}
cout<<"\n";
}
return 0;
}
F.Juju and Binary String
题意:有一串01串,该S串的值定义为
n
u
m
1
n
\frac{num1}{n}
nnum1,你需要找k个连续字串拼成长度为
m
m
m的串,使得它们的值相同。
题解:
首先T字符串的1的值设为
x
x
x,
x
m
=
n
u
m
1
n
\frac{x}{m}=\frac{num1}{n}
mx=nnum1
若
n
u
m
1
∗
m
%
n
!
=
0
num1*m\%n!=0
num1∗m%n!=0无解。
否则
x
=
n
u
m
1
∗
m
/
n
x=num1*m/n
x=num1∗m/n转化为找k个字串使得子串的长度为
m
m
m,1的数量为
x
x
x
存在一个结论k一定<=2;分类讨论。
我们现在还假设这个字符串是圆形的。考虑这个圆形字符串中任何长度为m的子数组,包括环绕的子数组。如果我们能找到一个恰好有 x x x个 1 1 1的子数,那么我们需要的最小零件数是 1 1 1或 2 2 2,这取决于它是否环绕。而这两者都可以用滑动窗口/前缀和来检查。
事实证明,这样的子阵列总是存在的。如果 i ≤ n − m + 1 i≤n-m+1 i≤n−m+1,让 c i c_i ci为 s [ i . . . i + m − 1 ] s[i...i+m-1] s[i...i+m−1]中的 1 1 1的数量,否则为 s [ 1... m − ( n − i + 1 ) ] + s [ i . . . n ] s[1...m-(n-i+1)]+s[i...n] s[1...m−(n−i+1)]+s[i...n](即,环绕的子阵列与不环绕的子阵列)。请注意,对于所有 1 ≤ i < n 1≤i<n 1≤i<n的情况, ∣ c i + 1 − c i ∣ ≤ 1 , ∣ c 1 − c n ∣ ≤ 1 |ci+1-ci|≤1,|c1-cn|≤1 ∣ci+1−ci∣≤1,∣c1−cn∣≤1。这是因为如果我们把我们考虑的子数组从 i i i滑到 i + 1 i+1 i+1,我们就排除了 s i si si到 s ( ( i + m − 1 ) m o d n ) + 1 s((i+m-1)mod\ n)+1 s((i+m−1)mod n)+1。这意味着,对于所有 m i n ( c i ) ≤ y ≤ m a x ( c i ) min(c_i)≤y≤max(c_i) min(ci)≤y≤max(ci),都存在 c i = y c_i=y ci=y,因为没有办法在 "跳过 "中间的值的同时,从一个小 c i c_i ci到大 c i c_i ci。
假设 m a x ( c i ) ≥ x max(c_i)≥x max(ci)≥x和 m i n ( c i ) ≤ x min(c_i)≤x min(ci)≤x,意味着某些 c i = x c_i=x ci=x。假设 m a x ( c i ) < x max(c_i)<x max(ci)<x。这意味着 ∑ i = 1 n c i < n ⋅ x \sum_{i=1}^nci<n⋅x ∑i=1nci<n⋅x。此外,每个索引正好包括在 m m m个不同的子阵列中。因为 ∑ i = 1 n c i = m ⋅ n u m 1 \sum_{i=1}^nci=m⋅num1 ∑i=1nci=m⋅num1。因此, m ⋅ n u m 1 < n ⋅ x m⋅num1<n⋅x m⋅num1<n⋅x,或者 x ∗ n > m ⋅ n u m 1 x*n>m⋅num1 x∗n>m⋅num1,这与 x x x的定义是矛盾的。 ∑ i = 1 n c i = m ⋅ n u m 1 > n ⋅ x \sum_{i=1}^nci=m⋅num1>n⋅x ∑i=1nci=m⋅num1>n⋅x,或者 x ∗ n < m ⋅ n u m 1 x*n<m⋅num1 x∗n<m⋅num1,这是一个矛盾。因此, m a x ( c i ) ≥ x 和 m i n ( c i ) ≤ x max(c_i)≥x和min(c_i)≤x max(ci)≥x和min(ci)≤x。
int num[maxn];
void solve(){
int n,m;
cin>>n>>m;
string s;
cin>>s;
num[0]=0;
for(int i=0;i<n;i++){
num[i+1]=num[i]+(s[i]=='1');
}
ll k=(ll)num[n]*m;
if(k%n){
cout<<"-1\n";
return;
}
k/=n;
int index=-1;
for(int i=1;i+m-1<=n;i++){
if(num[i+m-1]-num[i-1]==k){
index=i;
break;
}
}
if(index!=-1){
cout<<1<<"\n";
cout<<index<<" "<<index+m-1<<"\n";
return;
}
for(int i=1;i+(n-m)-1<=n;i++){
if(num[i+(n-m)-1]-num[i-1]==(num[n]-k)){
index=i;
break;
}
}
cout<<2<<"\n";
cout<<1<<" "<<index-1<<"\n";
cout<<index+(n-m)<<" "<<n<<"\n";
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}