green_gold_dog, array and permutation(Problem - A - Codeforces)
题目大意:现有一个数组a[]和一个排列b[],要求使输出一个使ci=ai-bi中ci不同值最多的排列b[].
思路:因为这里不涉及绝对值,所以我们直接将a从小到大排序然后b从大到小赋值即可。为了保证顺序,记录一下a每个值的下标,赋值的时候,将值按照下标写入新数组。
#include<bits/stdc++.h>
using namespace std;
int a[200010];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,x;
scanf("%d",&n);
vector<pair<int,int>>p;
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
p.push_back({x,i});
}
sort(p.begin(),p.end());
for(int i=0;i<n;i++)
{
auto it=p[i].second;
a[it]=n-i;
}
for(int i=1;i<=n;i++) printf("%d ",a[i]);
printf("\n");
}
}
XOR Palindromes(Problem - B - Codeforces)
题目大意:有一个01串s,我们定义一个新的01串s1,要求两者对应位取异或后得到的是一个回文串,现在我们只需要知道s1中1的个数,并定义一个新串t,如果的s1中1的个数可以为i,那么t[i]=1,否则t[i]等于0,输出t.
思路:首先找出将s变成回文串的最小操作,0^1=1,0^0=0,1^1=0,所以,s中对应位置不相同的,其中一个位置对应的s1中就是1,那么1的个数就多1.然后如果变成回文串后,那么奇数长度则s1中可以再多加任意多个1,偶数长度则每次加两个1即可。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
string s;
cin>>s;
int t1=0,t0=0,y=0;
for(int i=0,j=n-1;i<j;i++,j--)
{
if(s[i]==s[j]&&s[j]=='0') t0++;
else if(s[i]==s[j]&&s[j]=='1') t1++;
else y++;
}
if(y==0)
{
if(n%2)
{
for(int i=1;i<=n+1;i++) printf("1");
}
else
{
for(int i=0;i<=n;i++)
{
if(i%2) printf("0");
else printf("1");
}
}
}
else
{
string s1="";
for(int i=0;i<=n;i++) s1 += '0';
s1[y]='1';
int r=n-2*y;
if(r%2)
{
for(int i=1;i<=r;i++) s1[y+i]='1';
}
else
{
for(int i=2;i<=r;i+=2) s1[y+i]='1';
}
cout<<s1;
}
cout<<endl;
}
}
Salyg1n and the MEX Game(Problem - C - Codeforces)
题目大意:一道交互题,现有一个数组a[],A,B做游戏,A可以往里面添加任意一个不在a[]中的非负数,B可以移走任意一个存在于数组中,同时严格小于A的非负数。当B不能执行操作时,游戏结束,得分为mex(a)(不在a[]中的最小非负数),A想要最大化结果,B想要最小化结果,你代表A,oj代表B,清完成交互过程。
思路:这道题思路比较简单,我们先找出mex(a),然后输出这个值,那么oj会输入一个小于这个值的值,我们只用输出和oj输出的值一样的值即可。原理就是,先找出初始mex,如果这里不补的话,那么即使插入更大的值,结果也是这个,所以需要补一下,然后B移走更小的值,那么我们再补即可,因为B能动的数严格小于A放的数,那么不会是死循环。重点就是输入输出的处理,毕竟是交互题。
#include<bits/stdc++.h>
using namespace std;
int s[100010];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,c;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&s[i]);
int mi=0;
for(int i=1;i<=n;i++)
{
if(s[i]!=mi) break;
mi++;
}
printf("%d\n",mi);
fflush(stdout);
while(1)
{
scanf("%d",&c);
if(c==-1) break;
printf("%d\n",c);
fflush(stdout);
}
if(c==-1) continue;
}
return 0;
}
Cyclic Operations(Problem - D - Codeforces)
题目大意:给定一个长为n的数组a[],数组的初值全为0,我们每次可以在n的范围内任选k个数l1,l2,...,lk,可以对数组a[]执行操作:a[li]=l[i%k+1],先给定一个数组b[],问能否从a[]转化成b[]。
思路:这种需要进行操作的题目,我们就先分析操作的本质,也就是操作究竟干了什么,先举个例子,如果l=[1,5,6],那么就是a[1]=5,a[5]=6,a[6]=1,相当于内部转一圈,也就是一个循环,如果我们进行多个操作,而且操作与操作之间还有重叠部分会怎样呢?如果只看数太过抽象,我们可以画一个图,在i和l[i]之间建一条有向边,表示i处的数从何而来:
这个图就很清楚,一个l[]可以形成一个环,当环与环重叠的时候,需要删一下边,最后得到的就是一个有环有链的图。我们如果从1开始遍历,那么到第二次到5的时候就会产生死循环,而且我们不管是从哪里开始遍历,都会以环作为结尾。而且环的大小一定是k,因为最后被更新的k个数之间才能构成完整的环。所以我们可以假设给定的数组b[]就是一个合法的数组,然后通过遍历的方式来找环。思路显然可以,但是我们遇到环了肯定要判断退出,那么就要标记一下,比如我们从1开始遍历,那么遍历结束会是在第二次访问到5的时候,然后,我们再从2开始遍历,如果将之前打的标记全部清干净,3到1之后,再继续,然后又是到5结束,那么此时记录的长度就是6,而非是3,判断不是特别方便,而且清标记的过程时间复杂度是O(n),嵌套在遍历的O(n)循环中,那么就是O(n^2)是会超时的。所以我们标记不能清,那么遍历的1就会截至,此时的长度虽然是3,但是不成环,如果以长度作为判断标准,那么前面的那个循环长度为4,就不合适。所以我们提出一个新的思路:
将每一次的遍历打上不同的标记。如同上面用不同颜色标识一样,每次遍历从1开始计数,同时将每个数第一次被遍历到时的计数记录下来,比如5第一次被遍历到时的计数是2,第二次被遍历到时计数是5,此时已经退出,然后用计数减去退出位置的第一次计数,这里的值就是环的大小,判断这个环的大小是否为k。不清标记的话,那么从2开始遍历到1结束,这里就不用判环,只用看1是否是在这层中被打上标记的,如果不是,那么就不用考虑它(对应环重叠部分不会影响未重叠部分的计数),直接开始下一次遍历。这样遍历之后,但凡有一个大小不为k的环,就说明不是通过这种操作得到的,那么就判否。
#include<bits/stdc++.h>
using namespace std;
int a[200010],ne[200010],num[200010],st[200010];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
ne[i]=a[i];
st[i]=0;
}
if(k==1)
{
int flag=1;
for(int i=1;i<=n;i++)
{
if(ne[i]!=i)
{
flag=0;
break;
}
}
if(flag) printf("YES\n");
else printf("NO\n");
continue;
}
int tag=1;//标识第几次遍历
int flag=1;
for(int i=1;i<=n;i++)
{
if(!st[i])
{
int idx=i;
int c=1;
while(!st[idx])
{
st[idx]=tag;
num[idx]=c++;
idx=ne[idx];
}
if(st[idx]!=tag)
{
tag++;
continue;
}
if(c-num[idx]!=k)
{
flag=0;
break;
}
tag++;
}
}
if(flag) printf("YES\n");
else printf("NO\n");
}
}
ps:这道题有两个延伸,一个这个思路最裸的用法,用来判断图中是否有环,以及图中各个环的大小,其实各个环可能有点用不了,因为我们这里其实将1-3之间的边断掉了,如果不断掉,实际上判不了,当然由于判环的思路比较简单,一个点在遍历的过程中被遍历两次就算有环,所以倒也不用这么麻烦,这里权作类比。另一个延伸就是用来处理区间覆盖问题,下次遇到区间覆盖的问题,可以从图的角度来考虑一下。
Salyg1n and Array (simple version)(Problem - E1 - Codeforces)
题目大意:这是一道交互题,现有一长度为n的数组a[],给定k,我们每次可以进行询问,询问[i,i+k-1]范围的异或和,每次询问后,这段区间就会被反转一次,最多进行100次询问,要求输出数组的异或和。(另外n和k都是正偶数)。
思路:首先对于n%k==0的情况就很简单,从1开始询问,每次跳跃k,将每次的询问值异或即可。比较麻烦的就是不能整除的情况,即r=n%k,后r个元素。
如图所示,我们可以重复利用已查询区间的最后一小段,进而往后将剩余部分查出来,只要保证查询偶数次,那么重复计算异或和的部分就会被抵消掉。上图示例是只往后拓展一个,我们实际可以将最后一部分分成两块,查询两次得到,那么问题就解决了。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,k;
scanf("%d%d",&n,&k);
if(n%k==0)
{
int i,ans=0;
for(i=1;i+k-1<=n;i+=k)
{
cout<<"? "<<i<<endl;
fflush(stdout);
int c;
cin>>c;
ans ^= c;
}
cout<<"! "<<ans<<endl;
fflush(stdout);
}
else
{
int i,ans=0;
for(i=1;i+k-1<=n;i+=k)
{
cout<<"? "<<i<<endl;
fflush(stdout);
int c;
cin>>c;
ans ^= c;
}
int r=n-i+1;
int ck=r/2;
i=i+ck-1-k+1;
for(;i+k-1<=n;i+=ck)
{
cout<<"? "<<i<<endl;
fflush(stdout);
int c;
cin>>c;
ans ^= c;
}
cout<<"! "<<ans<<endl;
}
}
}
Salyg1n and Array (hard version)(Problem - E2 - Codeforces)
题目和上面差不多,不过变成最多只能查询57次而已,但是上面讨论的时候,已经将情况最优化了,所以这里可以直接用上面的代码。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,k;
scanf("%d%d",&n,&k);
if(n%k==0)
{
int i,ans=0;
for(i=1;i+k-1<=n;i+=k)
{
cout<<"? "<<i<<endl;
fflush(stdout);
int c;
cin>>c;
ans ^= c;
}
cout<<"! "<<ans<<endl;
fflush(stdout);
}
else
{
int i,ans=0;
for(i=1;i+k-1<=n;i+=k)
{
cout<<"? "<<i<<endl;
fflush(stdout);
int c;
cin>>c;
ans ^= c;
}
int r=n-i+1;
int ck=r/2;
i=i+ck-1-k+1;
for(;i+k-1<=n;i+=ck)
{
cout<<"? "<<i<<endl;
fflush(stdout);
int c;
cin>>c;
ans ^= c;
}
cout<<"! "<<ans<<endl;
}
}
}