[CF1362ABC,CF1361AB]Codeforces Round #647 (Div. 2)
赛后发现C被fst了/kk
本来能上分六十多,结果就上了十几
CF1362A Johnny and Ancient Computer
https://codeforces.com/problemset/problem/1362/A
比赛的时候写麻烦了,其实还有更简单的实现方法
inline int get(int x){
int ret=0;
ret+=x/3;x%=3;
ret+=x/2;x%=2;
ret+=x;
return ret;
}
int main(){
// std::printf("%d %d %d %d\n",get(1),get(8),get(7),get(12));
int T=read();while(T--){
LL a=read(),b=read();
int cnta=0,cntb=0,f=1;
while(a^b){
if(a>b){
if(a&1){
f=0;break;
}
a>>=1,cnta++;
}
else{
if(b&1){
f=0;break;
}
b>>=1,cntb++;
}
}
if(!f) std::puts("-1");
else std::printf("%d\n",get(cnta)+get(cntb));
}
return 0;
}
CF1362B Johnny and His Hobbies
https://codeforces.com/problemset/problem/1362/B
给出一个集合,选出一个最小的正整数,使得用它异或集合内的每一个数,所得所有数组成的集合与原集合相同
\(|S|\le 1024,S_i\le 1024\)
对于任意 \(S_i\),\(S_i\oplus k\) 如果也在 \(S\) 中,那么 \(k\) 符合要求
因为集合中的数互不相同,而两个不相同的数,异或后的结果必然不同
所以从 \(1\) 开始枚举 \(k\),直到成立
那么如何知道它在什么情况下一定不成立?只要比 \(\max S_i\) 的二进制位多一位就可以
多出来的那一位 \(1\),无法被异或掉(其它的数在这一位都是 \(0\)),也不可能等于任何原集合中的数
为了方便直接枚举到了最大数乘二
int t[10005],n;
int main(){int T=read();while(T--){
n=read();
int max=0;
for(reg int a,i=1;i<=n;i++){
a=read();
t[a]=1;max=std::max(max,a);
}
int maxx=max*2;
for(reg int f,i=1;i<=maxx;i++){
f=1;
for(reg int j=0;j<=max;j++)if(t[j]&&!t[j^i]){
f=0;break;
}
if(f){
std::printf("%d\n",i);
goto YES;
}
}
std::puts("-1");
YES:;
std::memset(t,0,sizeof t);
}
return 0;
}
CF1362C Johnny and Another Rating Drop
https://codeforces.com/problemset/problem/1362/C
问从 \(0\) 到 \(n\),每两个相邻的数的二进制表示中不同位数的个数和,是多少
\(n\le 10^{18}\)
一开始强行找了一波规律,发现并不明显
然后打了个表,放到oeis上,发现还真有
按照第一个公式写的,就是对于 \(n=2m+1\),答案是:
其中,\(q=\lfloor\log_2 m\rfloor\),而第 \(2m\) 项是第 \(2m+1\) 项减一
这玩意可以 \(O(\log n)\) 来算,所以写了一发交上去pretest过了,结果第二天以看fst了/kk,并不知道为什么
当然这个我也不会证明
然后才发现还有一个更简单的公式,答案是
也是 \(O(\log n)\),赛后按这个写过了
这个还是可以稍微说明一下的,把它理解为对于每个二进制位一位一位地看
对于第 \(k\) 个(从 \(0\) 开始编号)二进制位(位权 \(2^k\)),那么其对答案的贡献是 \(\lfloor\dfrac{n}{2^k}\rfloor\)
也就是说,每 \(2^k\) 个数,\(k\) 位的数字会更改一次,对答案产生 \(1\) 的贡献
比如 \(k=2\)
001
010
011
100 <----改变了一次
101
110
111
(1)000<------改变了一次
为什么会这样应该挺好理解的,就是从 \(0\) 到 \(k\) 为上的所有数字累加一遍,就会改变一次
代码
signed main(){int T=read();while(T--){
LL n=read(),f=0;
if(n==1){
std::puts("1");continue;
}
LL ans=0;
for(reg LL now,k=1;;k*=2){
now=n/k;
if(!now) break;
ans+=now;
}
std::printf("%lld\n",ans);
}
return 0;
}
CF1361A Johnny and Contribution
https://codeforces.com/problemset/problem/1361/A
一个无相图,每个点有点权,点权小于等于点数
对于一个选择点的顺序,为每个点确定一个实际的点权为:在与他相邻的点中,没有出现过的,最小的正整数
确定任意一种顺序,使得实际点权与给定点权相等
\(n,m\le 5\cdot 10^5\)
考虑什么时候一个点可以被选择,满足以下条件
- 所有比它的点权小的权值,都在与它相邻的,已经被选的点中出现过
- 它的点权没有在上述点的点权中出现过,如果出现过,肯定无解
点权小的点,要么影响不到点权大的点,要么可以使得点权大的点在考虑上述条件时成立
而点权大的点,无论如何影响不到点权小的点
所以按点权从小到大考虑这些点,判断它们是否满足要求
稍微说一下复杂度的事,用 exis[i]
表示点权为 \(i\) 的与当前点相邻,已经被选择的点(因为如果成立的话权值比他小的点都已经被选了,所以只要判权值大小就好了),是否存在
代码中的这一句话:
for(reg int k=1;k<t[u];k++)if(!exis[k]){f=0;break;}
因为最多有与 \(u\) 相邻的点数个数个数组元素被标为 \(1\),所以最多枚举 \(\min(t_u,\text{与 }u\text{ 相邻的点的个数})\) 次,所以复杂度是正确的
#define N 500005
#define M 1000005
struct graph{
int fir[N],nex[M],to[M],tot;
inline void add(int u,int v){
to[++tot]=v;
nex[tot]=fir[u];fir[u]=tot;
}
}G;
int n,m;
int ans[N];
int t[N],exis[N],que[N];
std::vector<int>ver[N];
int main(){
n=read();m=read();
for(reg int a,b,i=1;i<=m;i++){
a=read();b=read();
G.add(a,b);G.add(b,a);
}
for(reg int i=1,a;i<=n;i++){
a=read();
ver[a].push_back(i);t[i]=a;
}
for(reg int size,i=1;i<=n;i++){
size=ver[i].size();
for(reg int u,j=0;j<size;j++){
u=ver[i][j];
que[0]=0;
for(reg int v,k=G.fir[u];k;k=G.nex[k]){
v=G.to[k];
if(t[v]<t[u]) exis[t[v]]=1,que[++que[0]]=t[v];
else if(t[v]==t[u]) return std::puts("-1"),0;
}
int f=1;
for(reg int k=1;k<t[u];k++)if(!exis[k]){f=0;break;}
for(reg int k=1;k<=que[0];k++) exis[que[k]]=0;
if(f) ans[++ans[0]]=u;
else return std::puts("-1"),0;
}
}
for(reg int i=1;i<=n;i++) std::printf("%d ",ans[i]);
return 0;
}
CF1361B Johnny and Grandmaster
https://codeforces.com/problemset/problem/1361/B
给你 \(p\) 和 \(k_i\),把所有的 \(p^{k_i}\) 划分为两组,使这两组内的和,的差的绝对值最小。答案即这个绝对值,对 \(10^9+7\) 取模
\(p,n,k_i\le 10^6\)
可以把一组里的数设成正的,一组里的设成负的,这样答案就是所有数的和的绝对值了
一下就按照这样的思路来描述
给 \(k_i\) 排个序,先选一个最大的 \(p^{k_i}\) 加入答案
然后对于所有的 \(k_j\le k_i\),从大到小去枚举,让答案减去 \(p^{k_j}\)
答案肯定要么把所有 \(p^{k_j}\) 都减完也大于零,要么在某一时刻等于零
因为 \(p^{k_i}\) 放到 \(k\) 进制就是 \((1000\cdots)_p\)(或者更普遍的对于 \(a\cdot p^{k_i}\),表示为 \((a000\cdots)_p\)),如果当前有一个 \(k_j=k_i\),那一减就成 \(0\) 了
而如果没有,那么 \(k_j<k_i\) 的 \(k_j\) 显然不会减到负数
那只要当前答案是 \(0\),就加上 \(p_{k_i}\),否则减就行了
因为要取模,所以同时要维护一个模其它数的答案,只有两个答案都是 \(0\),才能确定答案实际上(取模之前)是 \(0\)
#define mod 1000000007
#define Mod 1000000003
int n,p,a[1000005];
inline int power(int a,int b,int p){
reg int ret=1;
while(b){
if(b&1) ret=((LL)ret*a)%p;
a=((LL)a*a)%p;b>>=1;
}
return ret;
}
int main(){int T=read();while(T--){
n=read();p=read();
for(reg int i=1;i<=n;i++) a[i]=read();
std::sort(a+1,a+1+n);
int ans1=0,ans2=0;
for(reg int i=n;i;i--){
if(!ans1&&!ans2){
ans1=((LL)ans1+power(p,a[i],mod))%mod;
ans2=((LL)ans2+power(p,a[i],Mod))%Mod;
}
else{
ans1=((LL)ans1-power(p,a[i],mod)+mod)%mod;
ans2=((LL)ans2-power(p,a[i],Mod)+Mod)%Mod;
}
}
std::printf("%d\n",ans1);
}
return 0;
}