密钥(key)
Description
在这个问题中,一个密钥是指一个长度为\(3n\)的二进制序列,其中\(n\)是正整数。
序列的每一位从左往右依次被编号为\(1\)到\(3n\) ,一个密钥的权值是指数字不同的相邻位的个数再加上\(1\) 。比如:
\(000\) 的权值是 \(1\), \(011010100\) 的权值是 \(7\)。密钥可以被修改。确切地说,你可以不断地进行下面的操作:任选两个相邻的位,然后同时将它们取反。例如,可以通过一次操作把 \(000\) 修改为 110 。
给定一个长度为\(3n\)的密钥,请通过不超过\(n\)次操作,将其修改为一个权值不少于\(2n\)的密钥。你可以认为合法方案必然存在。
Input
输入一行,包含一个长度为\(3\)的倍数的\(01\)序列。
Output
第一行包含一个整数\(m\),表示操作的次数,你需要保证\(0 \leq m \leq n\) 。
第二行包含\(m\)个正整数 \(a_1,a_2,\dots,a_m(1 \leq a_i < n)\),依次表示每次翻转第\(a_i\) 和第\(a_{i+1}\)位。
如果初始密钥的权值已经不小于\(2n\)你可以仅输出一行一个整数\(0\)。
xjb分析
玄学做法 \(50pts\)
? 考虑到对答案有贡献的情况为当前位置与下一位置字符不同.
我们\(O(n)\)枚举位置,判断当前位置\(i\)是否与下一位置\(i+1\)相同.
如果不同,我们可以尝试翻转这两个位置,但是想要对答案有贡献?我们需要再判断\(i\)位置与\(i+2\)位置与\(i-1\)的字符是否存在相同,再决定我们是否去变换这两个位置.
例如 :
我们这时候改变\(i\)位置,\(i+1\)位置的话,
这时候,对答案的贡献就会变多\(+1\)。
然后莫名其妙这样做就\(get\)了\(50pts\)
正解
看到\(3n\),我们考虑三个的情况.
存在这些情况.
- \(000\) 5.\(111\)
- \(001\) 6.\(110\)
- \(011\) 7.\(100\)
- \(010\) 8.\(101\)
我们发现,
\(2.6\)情况翻转中间位置可以使贡献更大.
\(3.7\)情况翻转最左边位置可以使贡献更大.
这个可以手试一下.
因此代码中简单地模拟一下即可 qwq。
代码
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<string>
#include<cstring>
#define R register
using namespace std;
char s[300008];
int len,cnt,ans[300008],val;
int main()
{
// freopen("key.in","r",stdin);
// freopen("key.out","w",stdout);
scanf("%s",s+1);
len=strlen(s+1);
for(R int i=1;i<=len;i++)
if(s[i]!=s[i-1] and i!=1)
val++;
if((val+1)>=2*(len/3))
{
puts("0");
exit(0);
}
for(R int i=1;i<=len-2;i+=3)
{
if(s[i]=='0' and s[i+1]=='0' and s[i+2]=='1')
{
ans[++cnt]=i+1;
s[i+1]='1';s[i+2]='0';
}
if(s[i]=='0' and s[i+1]=='1' and s[i+2]=='1')
{
ans[++cnt]=i;
s[i]='1';s[i+1]='0';
}
if(s[i]=='1' and s[i+1]=='1' and s[i+2]=='0')
{
ans[++cnt]=i+1;
s[i+1]='0';s[i+2]='1';
}
if(s[i]=='1' and s[i+1]=='0' and s[i+2]=='0')
{
ans[++cnt]=i;
s[i]='0';s[i+1]='1';
}
if(s[i]=='1' and s[i+1]=='1' and s[i+2]=='1')
{
if(i==1)ans[++cnt]=i;
else if(s[i-1]=='1')
{
ans[++cnt]=i;
s[i]='0';
s[i+1]='0';
}
else ans[++cnt]=i+1,s[i+1]='0',s[i+2]='1';
}
if(s[i]=='0' and s[i+1]=='0' and s[i+2]=='0')
{
if(i==1)ans[++cnt]=i;
else if(s[i-1]=='0')
{
ans[++cnt]=i;
s[i]='1';
s[i+1]='1';
}
else ans[++cnt]=i+1,s[i+1]='1',s[i+2]='0';
}
}
printf("%d\n",cnt);
for(R int i=1;i<=cnt;i++)
printf("%d ",ans[i]);
fclose(stdin);
fclose(stdout);
return 0;
}
最大公约数(gcd)
Description
Makik 是一名勤劳的学生。他在刚刚学完欧几里得算法时想多做一些练习题,于是在纸上写下了一个长度为\(n\)的排列。之后,他做了\(m\)轮练习。对于第\(i\)轮练习,他从排列中挑出一个区间\([l_i,r_i]\) ,并对处于这个区间中的元素两两求最大公约数,再找出这些最大公约数中的最大值。
Makik 已经成为最大公约数的大师了,而且仁慈地把这个练习方法介绍给了你。现在,请你也来做做看
Input
第一行包含两个数字\(n,m\),分别表示排列的长度和练习的轮数。
接下来一行\(n\)个数字,依次表示这个排列中的每个元素。
再接下来\(m\)行,其中第\(i\)行包含两个数字\(l_i,r_i\),表示在第\(i\)轮练习中挑出的区间。Output
输出\(m\)行,每行一个数字,表示该轮练习里区间内的元素两两间最大公约数中的最大值。
xjb分析
暴力
对于\(m\)次询问,直接枚举\([l_i,r_i]\)区间的数,两两求\(gcd\)取\(max\).
代码写法大致如下:
for(int i=1;i<=m;i++)
{
scanf("%d%d",&l,&r);
int now=0;
for(int j=l;j<=r;j++)
for(int k=j+1;k<=r;k++)
now=max(now,gcd(a[j],a[k]));
}
时间复杂度为\(O(m\times n^2 loga_i)\)
令人感到mmp的是,暴力部分全部\(TLE\)。
正解
考虑到对答案有贡献的是这些数的约数.
因此我们对这些约数搞事。
做法
对每个数求出约数,在一个区间内出现超过两次的约数,那么它肯定是某两个数的\(gcd\).
这样我们把所有出现次数\(\geq 2\)的约数加入线段树中维护最大值,并每次更新即可.
但是,如果在线做的话依旧不行.
我们考虑将询问离线.
如何离线?
考虑到我们必须要知道整个区间的数的约数的情况,所以我们对询问的右端点排序.
我们从小到大对右端点\(r_i\)进行排序,当遇到某个右端点的时候,输出ans即可.
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cctype>
#define ls o<<1
#define rs o<<1|1
#define N 100005
#define R register
using namespace std;
inline void in(int &x)
{
int f=1;x=0;char s=getchar();
while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
while(isdigit(s)){x=x*10+s-'0';s=getchar();}
x*=f;
}
struct cod{
int l,r,idx;
bool operator <(const cod&a)const
{
return r<a.r;
}
}que[100008];
int ans[N];
int n,m,a[N],tr[N<<3],exist[N];
inline void up(int o){tr[o]=max(tr[ls],tr[rs]);}
void build(R int o,R int l,R int r)
{
if(l==r)
{
tr[o]=0;
return;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
up(o);
}
void change(int o,int l,int r,int pos,int del)
{
// printf("%d %d %d %d %d",o,l,r,pos,del);
if(l==r){tr[o]=max(tr[o],del);return;}
int mid=(l+r)>>1;
if(pos<=mid)change(ls,l,mid,pos,del);
else change(rs,mid+1,r,pos,del);
up(o);
}
int query(int o,int l,int r ,int x,int y)
{
// printf("%d %d %d %d %d\n",o,l,r,x,y);
if(x<=l and r<=y)return tr[o];
int mid=(l+r)>>1,res=0;
if(x<=mid)res=max(res,query(ls,l,mid,x,y));
if(y>mid)res=max(res,query(rs,mid+1,r,x,y));
return res;
}
int main()
{
// freopen("gcd.in","r",stdin);
// freopen("gcd.out","w",stdout);
in(n),in(m);
{
for(R int i=1;i<=n;i++)in(a[i]);
build(1,1,n);
for(R int i=1;i<=m;i++)
in(que[i].l),in(que[i].r),que[i].idx=i;
sort(que+1,que+m+1);
int now=1;
for(R int i=1;i<=n;i++)
{
for(R int j=1;j*j<=a[i];j++)
{
if(a[i]%j==0)
{
if(exist[j])change(1,1,n,exist[j],j);//判断之前是否存在过
if(exist[a[i]/j])change(1,1,n,exist[a[i]/j],a[i]/j);//判断之前是否存在过
exist[j]=i,exist[a[i]/j]=i;
}
}
while(i==que[now].r and now<=m)
{
ans[que[now].idx]=query(1,1,n,que[now].l,que[now].r);
now++;
}
}
for(R int i=1;i<=m;i++)printf("%d\n",ans[i]);
}
fclose(stdin);
fclose(stdout);
return 0;
}
原文地址:https://www.cnblogs.com/-guz/p/9748068.html