心得
自己的cf不知道为什么交不了题了,用的归神的号还给归神掉分了,wtcl……
而且明显觉得自己前中期题代码比题解复杂好多,AC之后,还是补一补简单的做法比较好
CF的这种代码不长考验思维与1y的题,最培养综合能力了
思路来源
https://codeforces.com/blog/entry/67388
B.Ehab Is an Odd Person
Q:1e5个数,每个数在1到1e9之间,可以交换和为奇数的两个位置的数,输出最后字典序最小的序列
A:如果只有奇数或只有偶数,就直接输出原序列,
否则借助一个奇数一定可以令两个偶数换位,且奇数位置不变
数学归纳一下,就是可以令偶数内部排序,奇数内部排序,且奇数和偶数之间还可以任意换,那就是sort一下的结果
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,a[maxn],b[2];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
b[a[i]&1]++;
}
if(b[0]&&b[1])sort(a+1,a+1+n);
for(int i=1;i<=n;++i)
printf("%d%c",a[i],i==n?'\n':' ');
return 0;
}
C.Ehab and a Special Coloring Problem
Q:给下标为2到n的位置每个分配一个1到n之间的数,使得下标互质的(i,j)分配的数不同,且令最大的值最小,输出分配序列
A:注意到质数每个都需要新开一个数,且合数只需和其任意一个质因子的取值相同即可,
这样对于互质的两个合数来说,它们分配的数只与其质因子取值相同,而这两个合数没有公共的质因子,所以取值不同
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,a[maxn],cnt;
bool p[maxn];
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;++i)
{
if(!p[i])a[i]=++cnt;
for(int j=2*i;j<=n;j+=i)
a[j]=a[i],p[j]=1;
}
for(int i=2;i<=n;++i)
printf("%d%c",a[i],i==n?'\n':' ');
return 0;
}
D.Ehab and the Expected XOR Problem
Q:n(n<=18),给定一个x(1<=x<(1<<18)),要求输出一个长为len的序列,
序列的每个值只能在1到1<<n之间,在最大化len的前提下,
使得序列的任意一个子区间的异或值都不等于0或给定的x
A:考虑前缀异或和,则问题转化成,
任意一个子区间[L,R]的异或和=Xor[R]^Xor[L-1],
在前缀和序列中,即不能有两个下标i,j满足Xor(i)^Xor(j)==x,
①如果x在1到(1<<n)之间,在1到(1<<n)的序列中,异或值为x的(i,j)成对出现,互不影响
所以当异或前缀和用到了一个,就不能用另一个,把另一个标记用过即可
②如果x大于1<<n,任两个前缀值异或起来都不为x,随便取
输出时输出相邻两项异或值,类似前缀和作差还原回去即可
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
bool used[1<<18];
int n,x,m;
int a[1<<18],cnt;
int main()
{
scanf("%d%d",&n,&x);
m=1<<n;m--;
used[x]=1;used[0]=1;
for(int i=m;i>=1;--i)
if(!used[i])
{
a[++cnt]=i;
used[i^x]=1;
}
printf("%d\n",cnt);
for(int i=1;i<=cnt;++i)
printf("%d%c",a[i]^a[i-1],i==cnt?'\n':' ');
return 0;
}
E.Ehab and the Expected GCD Problem
Q:对于一个给定的1-n的排列,定义g(i)为其前i个数的gcd值,
定义f(p)为1到n这n个g(i)的值中数字不同的数的个数,fmax(p)为所有排列p中f(p)最大的值
给定n(2<=n<=1e6),求有多少排列p满足,f(p)==fmax(p),答案%(1e9+7)
A:
考虑到连续子段gcd会降,一次最少降一个2,降log次就降没了
所以,能降最多次数的话,就是一次降一个2,那么开头的数应该是不大于n的2的最大幂次
这个数把其中一个2替换成3也是可以的,如果新出的这个数不超过n的话,
替换成别的质数就不如替换成两个2或更多的2更优了,
定义dp[i][x][y]为当前放到第i个数,当前gcd为的方案数
定义f(x,y)为不超过n的的倍数的个数,显然
①可以往后加一个的倍数,后面还没用过的的倍数个数是f(x,y)-i
②可以往后加一个的倍数,且不能是的倍数,不然和①重复了
③可以往后加一个的倍数,且不能是的倍数,不然和①重复了
考虑初始情况,第一个值可以填不超过n的最大的2的幂次,dp[1][x][0]=1
如果可以在不超过n的前提下,把其中一个2换成3的话,dp[1][x-1][1]=1,
由于序列中一定会有1,所以最后序列尾的gcd一定为1,答案即为dp[n][0][0]
想了很久,f(x,y)-i变负数了咋办,后来觉得是在f(x,y)-i恰为0时,后面的dp[i+1][x][y]就恒为0,不会再被更新了
只会降序更新,f(x,y)-i为0的时候,说明x'>=x或y'>=y的f(x',y')-i<=0,dp[i][x'][y']早已是0了,不会用0去更新dp[i+1][x][y]的
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=1e6+10;
int n,p;
int dp[maxn][21][2];
int f(int x,int y)
{
int tmp=(1<<x);
if(y)tmp*=3;
return n/tmp;
}
int main()
{
scanf("%d",&n);
while((1<<p)<=n)p++;
p--;
dp[1][p][0]=1;
if((1<<(p-1))*3<=n)
dp[1][p-1][1]=1;
for(int i=1;i<n;++i)
{
for(int x=0;x<=p;++x)
{
for(int y=0;y<=1;++y)
{
dp[i+1][x][y]=(dp[i+1][x][y]+1LL*dp[i][x][y]*(f(x,y)-i))%mod;
if(x)dp[i+1][x-1][y]=(dp[i+1][x-1][y]+1LL*dp[i][x][y]*(f(x-1,y)-f(x,y)))%mod;
if(y)dp[i+1][x][y-1]=(dp[i+1][x][y-1]+1LL*dp[i][x][y]*(f(x,y-1)-f(x,y)))%mod;
}
}
}
printf("%d\n",dp[n][0][0]);
return 0;
}