Codeforces Round #563 (Div. 2)
D:
题意是给定
n
n
n 和
x
x
x ,构建这样最长的数组
a
a
a 满足:
1
≤
a
i
≤
2
n
1\leq a_i \leq 2^n
1≤ai≤2n 且任意一个连续区间的异或和不等于
0
0
0 或
x
x
x 。
一个区间[l, r]的异或和可以由l-1的异或前缀和和r的异或前缀和异或起来表示。
- 连续区间异或和不为0则说明构造出的数组中,没有任何一个点的异或前缀和相等。
- 连续区间异或和不为 x x x 则表示,数组中每个点的异或前缀和异或上 x x x,不等于任何一个点的异或前缀和。
综合以上两点可知每一个点的异或前缀和和异或前缀和异或上x两两不等。因此可以通过已知的异或前缀和构造出整个数组。当 x > 2 n x > 2^n x>2n 时,不受限制2的影响。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6;
bool vis[N];
int p[N];
int a[N];
int main() {
int n, x;
scanf("%d%d", &n, &x);
int m = 0;
if(x>=(1<<n)) {
// 不可能有区间异或为x,所有前缀和可用
for(int i=1; i<(1<<n); ++i) {
p[m++]=i;
}
} else {
// 排除互斥
vis[0]=vis[x]=true;
for(int i=0; i<(1<<n); ++i) {
if(vis[i]) continue;
vis[i]=vis[i^x]=true;
p[m++]=i;
}
}
a[0]=p[0];
for(int i=1; i<m; ++i) {
a[i]=p[i-1]^p[i];
}
printf("%d\n", m);
for(int i=0; i<m; ++i) {
printf("%d%c", a[i], i+1==m?'\n':' ');
}
}
E:
题意是给定一个
n
n
n ,需要构造一个
n
n
n 的排列使得不同前缀gcd的数量最多,求有多少种构造方法。
gcd从前求到后,每次遇到一个新元素,gcd要么不变要么减少,减少就多了一个不同的前缀gcd值。因此要使得不同前缀gcd的数量最多,第一个数一定是
2
x
3
y
2^x3^y
2x3y ,如果还有大于3的素因子
p
p
p ,可以除掉这个素因子乘上个
4
4
4 ,使得不同前缀gcd的数量增加。同时
y
y
y 一定小于等于1,否则可以除掉
9
9
9 再乘上
8
8
8 ,增加不同前缀gcd的数量。
因此设 dp[i][x][y]
为第
i
i
i 个点,当前前缀gcd为
2
x
3
y
2^x3^y
2x3y 时的方案数。每次转移要么使gcd不变,要么使gcd减少。设 f(x, y)
为1~n中是
2
x
3
y
2^x3^y
2x3y 倍数的数量,得转移方程如下:
dp[i+1][x][y]+=dp[i][x][y]*(f(x, y)-i)
dp[i+1][x-1][y]+=dp[i][x][y]*(f(x-1, y)-f(x, y))
dp[i+1][x][y-1]+=dp[i][x][y]*(f(x, y-1)-f(x, y))
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e6+7;
int dp[N][20][2], p[20][2];
int n;
const int mod = 1e9+7;
// dp[i][x][y] 表示第i个数是2^x*3^y的倍数时的填充种类数。
int f(int x, int y) {
// 有多少2^x*3^y小于等于n
return n/p[x][y];
}
void add(int &x, int y, int z) {
int prod = 1LL*y*z%mod;
x = (x+prod)%mod;
}
int main() {
scanf("%d", &n);
int prod=1, cnt=0;
while(prod<=n) prod*=2, ++cnt;
prod/=2; --cnt;
dp[1][cnt][0]=1;
if(prod/2*3<=n) dp[1][cnt-1][1]=1;
p[0][0]=1; p[0][1]=3;
for(int i=1; i<20; ++i) {
for(int j=0; j<2; ++j) {
p[i][j]=p[i-1][j]*2;
}
}
for(int i=1; i<n; ++i) {
for(int x=0; x<20; ++x) {
for(int y=0; y<2; ++y) {
add(dp[i+1][x][y], dp[i][x][y], f(x, y)-i);
if(x>0) add(dp[i+1][x-1][y], dp[i][x][y], f(x-1, y)-f(x, y));
if(y>0) add(dp[i+1][x][y-1], dp[i][x][y], f(x, y-1)-f(x, y));
}
}
}
printf("%d\n", dp[n][0][0]);
return 0;
}
F:
首先树剖求出重链。从根
1
1
1 开始,假设其重链的深度最大点(肯定为叶子)为
u
u
u ,
x
x
x 到根的路径在
y
y
y 点与重链相交。可以得到
d
(
x
)
+
d
(
u
)
−
2
×
d
(
y
)
=
d
i
s
(
x
,
u
)
d(x)+d(u)-2 \times d(y) =dis(x, u)
d(x)+d(u)−2×d(y)=dis(x,u) ,由于
d
(
x
)
,
d
(
u
)
d(x), d(u)
d(x),d(u) 已知,因此通过询问
d
i
s
(
x
,
u
)
dis(x, u)
dis(x,u) 就可以得到
d
(
y
)
d(y)
d(y) 了,然后通过重链回溯可以找到
y
y
y 。如果
d
(
y
)
d(y)
d(y) 等于
d
i
s
(
x
,
u
)
dis(x, u)
dis(x,u) ,则说明
y
y
y 就是
x
x
x 。找到
y
y
y 之后,可以通过 s
询问跳轻边。由于一条路径上的重链和轻边都是
O
(
l
o
g
n
)
O(logn)
O(logn) 级别的,因此重链轻边都最多为 17。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
int fa[N], son[N], sz[N], d[N], dx, dy;
bool xisy, xsony;
vector<int> adj[N];
void dfs1(int u, int p, int deep) {
fa[u]=p;
d[u]=deep;
sz[u]=1;
for(int v : adj[u]) {
if(v==p) continue;
dfs1(v, u, deep+1);
sz[u]+=sz[v];
if(son[u]==-1||sz[son[u]]<sz[v]) son[u]=v;
}
}
void init() {
memset(son, -1, sizeof(son));
}
int queryS(int u) {
printf("s %d\n", u);
fflush(stdout);
scanf("%d", &u);
return u;
}
int queryD(int u) {
printf("d %d\n", u);
fflush(stdout);
scanf("%d", &u);
return u;
}
void dfs(int u) {
// printf("dfs: %d %d %d\n", u, dy);
if(son[u]!=-1) {
dfs(son[u]);
} else {
int dux=queryD(u);
dy=(d[u]+dx-dux)/2;
if(d[u]-dy==dux) xisy=true;
if(d[u]-dy+1==dux) xsony=true;
}
if(d[u]==dy) {
if(xisy) {
printf("! %d\n", u);
} else {
int v = queryS(u);
if(xsony) printf("! %d\n", v);
else dfs(v);
}
}
}
int main() {
int n;
scanf("%d", &n);
for(int i=1; i<n; ++i) {
int u, v;
scanf("%d%d", &u, &v);
adj[u].push_back(v);
adj[v].push_back(u);
}
init();
dfs1(1, 0, 0);
dx=queryD(1);
if(dx==0) {
printf("! 1\n");
fflush(stdout);
} else {
dfs(1);
}
return 0;
}