总体难度递增。感觉 E 题的做法比较新奇。F 题在 51nod 有强化版本。
题意:给定 n n n 个点,第 i i i 个点有权值 a i a_i ai。如果对于 i , j i, j i,j 有 a i and a j a_i \operatorname{and} a_j aiandaj 不为 0 0 0,那么 i , j i, j i,j 间有无向边,边权为 lowbit ( a i and a j ) \operatorname{lowbit}(a_i \operatorname{and} a_j) lowbit(aiandaj)。问从 1 1 1 到 n n n 的最短路。
我们可以依次考虑每一个位,然后把点权有相同位的点互相连一条边。但这样边的数目可能是 O ( n 2 ) O(n^2) O(n2)。
更好的做法是对当前位建立一个虚拟点,设为 u u u。遍历所有点,如果 i i i 的点权满足这一位上为 1 1 1,那么连一条从 i i i 到 u u u 的边,边权为这一位对应的二进制数;再连一条从 u u u 到 i i i 的边,边权为 0 0 0。这样边数就下降到了至多 64 n 64n 64n。跑 Dijkstra 可以通过。
本题题解区还有些更高妙的做法。
#include
#define REP(temp, init_val, end_val) for (int temp = init_val; temp <= end_val; ++temp)
#define REPR(temp, init_val, end_val) for (int temp = init_val; temp >= end_val; --temp)
using namespace std;
typedef long long ll;
int read(){
int f = 1, x = 0;
char c = getchar();
while (c < '0' || c > '9'){if(c == '-') f = -f; c = getchar();}
while (c >= '0' && c <= '9')x = x * 10 + c - '0', c = getchar();
return f * x;
}
int n;
unsigned a[100005];
ll dis[100005 + 50];
priority_queue<pair<int, ll>, vector<pair<int, ll> >,
greater<pair<int, ll> > > pq;
int to[6400005], nxt[6400006], at[100005 + 50], cnt;
unsigned w[6400005];
void init(){
n = read();
REP(i, 1, n) scanf("%u", &a[i]);
// build graph
memset(at, 0, sizeof(at));
cnt = 0;
int nn = n;
for (unsigned t = 1; t > 0; t <<= 1){
++n;
REP(i, 1, nn){
if (a[i] & t){
to[++cnt] = n, nxt[cnt] = at[i], w[cnt] = t, at[i] = cnt;
to[++cnt] = i, nxt[cnt] = at[n], w[cnt] = 0, at[n] = cnt;
}
}
}
}
void solve(){
memset(dis, 0x3f, sizeof(dis));
ll lim = dis[1];
dis[1] = 0;
pq.push(make_pair(1, 0));
for (; ; ){
while (!pq.empty()){
if (pq.top().second > dis[pq.top().first])
pq.pop();
else break;
}
if (pq.empty()) break;
int h = pq.top().first;
ll dd = pq.top().second;
pq.pop();
for (int i = at[h]; i; i = nxt[i]){
if (dd + w[i] < dis[to[i]])
dis[to[i]] = dd + w[i],
pq.push(make_pair(to[i], dis[to[i]]));
}
}
if (dis[n - 32] == lim){
printf("Impossible\n");
}else printf("%lld\n", dis[n - 32]);
}
int main(){
int T = read();
while (T--){
init();
solve();
}
return 0;
}
题意:给定一棵树, q q q 次询问,每次给出 l , r l, r l,r,求 max l ≤ u , v ≤ r dist ( u , v ) \max_{l \le u ,v \le r}\operatorname{dist}(u, v) maxl≤u,v≤rdist(u,v)。
由直径的性质:设对于树上点集 S S S,其内部两两点之间具有最长距离的点集为 F ( S ) F(S) F(S)。对于任意两个点集 S 1 , S 2 S_1, S_2 S1,S2,有 F ( S 1 ∪ S 2 ) ⊂ F ( S 1 ) ∪ F ( S 2 ) F(S_1 \cup S_2) \subset F(S_1) \cup F(S_2) F(S1∪S2)⊂F(S1)∪F(S2)。
这表明直径具有区间可合并性。从而我们可以用线段树或者 ST 表来维护一段区间内直径的两个端点和长度。要合并两个区间的答案时,新直径要么直接来自老直径,要么由两个直径各取一端点组成。
时间复杂度为 O ( ( n + q ) log n ) O((n+q)\log n) O((n+q)logn)。
51nod 上有强化版本:51nod 1766。当然做法是一样的。
#include
#define INF 2000000000
#define M 1000000007
using namespace std;
typedef long long ll;
int read(){
int f = 1, x = 0;
char c = getchar();
while(c < '0' || c > '9'){if(c == '-') f = -f; c = getchar();}
while(c >= '0' && c <= '9')x = x * 10 + c - '0', c = getchar();
return f * x;
}
int n, q;
int to[600005], w[600005], at[300005] = {0}, nxt[600005], cnt = 0;
int st[300005][20][2];
ll stlen[300005][20];
int fa[300005][20], dep[300005];
ll dist[300005];
void dfs(int cur, int f){
fa[cur][0] = f;
for (int j = 1; j < 20; ++j){
if (!fa[cur][j - 1]) break;
fa[cur][j] = fa[fa[cur][j - 1]][j - 1];
}
for (int i = at[cur]; i; i = nxt[i]){
int v = to[i];
if (v == f) continue;
dep[v] = dep[cur] + 1;
dist[v] = dist[cur] + w[i];
dfs(v, cur);
}
}
int get_lca(int x, int y){
if (dep[x] != dep[y]){
if (dep[x] < dep[y]) swap(x, y);
int diff = dep[x] - dep[y];
for (int t = 1, p = 0; diff > 0; t <<= 1, ++p)
if (t & diff)
x = fa[x][p], diff -= t;
}
if (x == y) return x;
for (int j = 19; j >= 0; --j)
if (fa[x][j] != fa[y][j])
x = fa[x][j], y = fa[y][j];
return fa[x][0];
}
ll get_dis(int x, int y){
int lca = get_lca(x, y);
return dist[x] + dist[y] - 2ll * dist[lca];
}
void update(int* uu, int* vv, ll stlen1, ll stlen2, int& u_new, int& v_new, ll& stllen){
u_new = v_new = 0, stllen = -1;
for (int u = 0; u < 2; ++u)
for (int v = 0; v < 2; ++v){
ll tmpdis = get_dis(uu[u], vv[v]);
if (tmpdis > stllen){
u_new = uu[u], v_new = vv[v], stllen = tmpdis;
}
}
if (stlen1 > stllen)
u_new = uu[0], v_new = uu[1], stllen = stlen1;
if (stlen2 > stllen)
u_new = vv[0], v_new = vv[1], stllen = stlen2;
}
void init(){
n = read(), q = read();
for (int i = 1; i < n; ++i){
int u = read(), v = read(), ww = read();
to[++cnt] = v, nxt[cnt] = at[u], w[cnt] = ww, at[u] = cnt;
to[++cnt] = u, nxt[cnt] = at[v], w[cnt] = ww, at[v] = cnt;
}
dep[1] = 0, dist[1] = 0, dfs(1, 0);
int logg = 0;
while ((1 << logg) < n) ++logg;
for (int i = 1; i <= n; ++i){
st[i][0][0] = st[i][0][1] = i, stlen[i][0] = 0;
}
for (int i = 1; i < logg; ++i){
int len = (1 << i >> 1);
for (int j = 1; j + len - 1 <= n; ++j){
update(st[j][i - 1], st[j + len][i - 1], stlen[j][i - 1], stlen[j + len][i - 1],
st[j][i][0], st[j][i][1], stlen[j][i]);
}
}
}
void solve(){
while (q--){
int l = read(), r = read();
if (l == r){
printf("0\n");
continue;
}
int logg = 0;
while ((2 << logg) < r - l + 1) ++logg;
int tmpu, tmpv;
ll res;
update(st[l][logg], st[r - (1 << logg) + 1][logg],
stlen[l][logg], stlen[r - (1 << logg) + 1][logg],
tmpu, tmpv, res);
printf("%lld\n", res);
}
}
int main(){
init();
solve();
return 0;
}
直径的性质之前用过,但是没有细究,遭报应了。
这场比赛坑点比较多,也交了 3 次 WA,只能说多多注意细节吧。