test20190929

闲扯

感觉思维还是没有打开, $T1$ 大体的想法都是对的,但是在最后的判重那里卡住了,结果实现方法貌似很简单的样子。。。

题面

见 $ftp$ 。

$T1$

Solution

我们有一个很暴力的想法:对于每一个点的答案,我们先枚举一下以哪种颜色出发,因为每条边可以走很多次,所以只要是它能到的点,都能够获得它们的贡献。

因为还可以执行一次换颜色的操作,我们枚举一下从哪一个它能到达的点以哪一种颜色出发,计算可以多出的贡献,然后取一个最大值即可。

这样做是 $O(n^3)$ 的,只能过第一个 $subtask$ 。

考虑一下怎么优化这个过程。

我们可以发现这样一个性质:我们只考虑一种颜色的话,原图就被分成了几个连通块,每一个连通块里的所有点,以该颜色出发,能够获得的贡献是一样的。

换颜色的操作就相当于是将不同颜色的图中的两个连通块的贡献合并。但是我们不能简单的直接相加,因为每一个点的贡献只能算一次。

如果直接合并两个连通块,复杂度显然是不对的,考虑换一种思路。

对于一个点,它在三种颜色的图中,一定属于三个不同的连通块,只有合并这三个中的两个时,我们才会重复计算这个点的贡献。

所以我们可以先预处理出合并两个连通块时重复计算的部分,合并时减去即可。

最后再算出从每个连通块出发的最大收获,查询时输出点 $u$ 所在的三个连通块的收获的最大值即可。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include<bits/stdc++.h>
#define del(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define mid ((l+r)>>1)
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
using namespace std;
template<class T>il read(T &x){
int f=1;char k=getchar();x=0;
for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
x*=f;
}
template<class T>il _print(T x){
if(x/10) _print(x/10);
putchar(x%10+'0');
}
template<class T>il print(T x){
if(x<0) putchar('-'),x=-x;
_print(x);
}
ll mul(ll a,ll b,ll mod){long double c=1.;return (a*b-(ll)(c*a*b/mod)*mod)%mod;}
it qpow(int x,int m,int mod){
int res=1,bas=x%mod;
while(m){
if(m&1) res=(1ll*res*bas)%mod;
bas=(1ll*bas*bas)%mod,m>>=1;
}
return res%mod;
}
const int MAXN = 1e5+5;
int n,m,q,u,v,ty,val[MAXN],head[MAXN][3],num_edge[3],bl[MAXN][3],cnt;
ll sum[MAXN*3],ans[MAXN*3];
map<pair<int,int>,ll> mp;
struct Edge{
int next,to;
Edge(){}
Edge(int next,int to):next(next),to(to){}
}edge[MAXN<<2][3];
il add_edge(int u,int v,int bl){
edge[++num_edge[bl]][bl]=Edge(head[u][bl],v),head[u][bl]=num_edge[bl];
edge[++num_edge[bl]][bl]=Edge(head[v][bl],u),head[v][bl]=num_edge[bl];
}
il DFS(int u,int ty){
bl[u][ty]=cnt,sum[cnt]+=val[u];
for(ri i=head[u][ty];i;i=edge[i][ty].next){
if(!bl[edge[i][ty].to][ty]) DFS(edge[i][ty].to,ty);
}
}
ill max(ll x,ll y){return x>y?x:y;}
ill max(ll x,ll y,ll z){return max(max(x,y),z);}
ill calc(int u,int x,int y){return sum[bl[u][x]]+sum[bl[u][y]]-mp[make_pair(bl[u][x],bl[u][y])];}
il init(){
for(ri i=1;i<=n;++i){
ans[bl[i][0]]=max(ans[bl[i][0]],calc(i,0,1),calc(i,0,2));
ans[bl[i][1]]=max(ans[bl[i][1]],calc(i,0,1),calc(i,1,2));
ans[bl[i][2]]=max(ans[bl[i][2]],calc(i,0,2),calc(i,1,2));
}
}
int main(){
freopen("grape.in","r",stdin);
freopen("grape.out","w",stdout);
read(n),read(m);
for(ri i=1;i<=n;++i) read(val[i]);
for(ri i=1;i<=m;++i){
read(u),read(v),read(ty),--ty;
for(ri j=0;j<3;++j) if(ty!=j) add_edge(u,v,j);
}
for(ri i=0;i<3;++i)
for(ri j=1;j<=n;++j)
if(!bl[j][i]) ++cnt,DFS(j,i);
for(ri i=1;i<=n;++i){
mp[make_pair(bl[i][0],bl[i][1])]+=val[i];
mp[make_pair(bl[i][1],bl[i][2])]+=val[i];
mp[make_pair(bl[i][0],bl[i][2])]+=val[i];
}
init();
read(q);
for(ri i=1;i<=q;++i) read(u),print(max(ans[bl[u][0]],ans[bl[u][1]],ans[bl[u][2]])),puts("");
return 0;
}

$T2$

Solution

因为每场比赛都有人被淘汰,所以将败者向胜者连边不会出现环;

因为有 $n$ 个人, $n-1$ 场比赛,所以一定是构成了一棵树。

将一条边的权值定义为这场比赛的精彩程度,那么第一问就等价于求一个原图的最小生成树。

第二问我们需要注意到一个性质,因为是取并,所以值一定是不增的。

那么我们将这条边加入原图中,一定会构成一个环,因为要保证是最小生成树,我们只需要去掉环上最大的边即可。

这样做可以过掉前 $5$ 个 $subtask$ 。

对于 $100\%$ 的数据,因为 $n\leq 10^5$ ,所以不能直接连边然后求最小生成树。

考虑换一种方式。

因为是求异或,我们可以考虑 $0/1\ Trie$ 。

先将每一个点的点权全部插入一颗 $0/1\ Trie$ 里面。

我们可以发现如下性质:对树上的一个节点,如果它的左右儿子里面有包含有数,我们将左儿子里的数和右儿子里的数分别看做是一个集合,那么我们必须用不少于 $1$ 条边合并这两个集合。

因为要求答案最小,所以我们选边一定是从小往大选的。

但是集合内部连边的代价一定是小于两个集合之间连边的(二进制的性质),所以我们最多选一条边。

因此我们枚举树上的每一个节点,对于所有需要连边的节点找出连的那一条边的权值,累加进答案即可。

找边的方法

对于一个需要连边的节点,我们设置两个指针分别指向该节点的左右儿子。

  1. 如果都能向左走,那么我们就都向左走,当前的贡献不变。
  2. 如果都能向右走,那么我们也想右走一次,当前的贡献不变。

如果上面两种都不行,我们再尝试如下两种:

  1. 左儿子向左,右儿子向右,当前贡献加上这一层的贡献。(具体看代码)
  2. 左儿子向右,右儿子向左,当前贡献加上这一层的贡献。
  • 这样做为什么是对的呢?

对于每一个节点,我们最多将它的子树中的所有点全部扫一遍。所以每一个节点,它最多被从它到根结点上的所有点扫一遍,因为 $0/1\ Trie$ 的深度是 $\log val$ 的,所以总复杂度是 $O(n\log n^2)$ 。

那么对于修改操作,我们只需要在 $Trie$ 上找到这两个点的 $LCA$ ,判断一下 $LCA$ 选取的边的取值和这条边的权值谁更小即可。

  • $ps:$ 对于找 $LCA$ ,我们不需要写倍增或者树剖,直接暴力跳即可。因为 $Trie$ 的深度是 $\log$ 的,所以最多跳 $\log$ 次。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<bits/stdc++.h>
#define del(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define mid ((l+r)>>1)
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
using namespace std;
template<class T>il read(T &x){
int f=1;char k=getchar();x=0;
for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
x*=f;
}
template<class T>il _print(T x){
if(x/10) _print(x/10);
putchar(x%10+'0');
}
template<class T>il print(T x){
if(x<0) putchar('-'),x=-x;
_print(x);
}
ll mul(ll a,ll b,ll mod){long double c=1.;return (a*b-(ll)(c*a*b/mod)*mod)%mod;}
it qpow(int x,int m,int mod){
int res=1,bas=x%mod;
while(m){
if(m&1) res=(1ll*res*bas)%mod;
bas=(1ll*bas*bas)%mod,m>>=1;
}
return res%mod;
}
const int MAXN = 1e5+5;
int n,m,val[MAXN],u,v,d,Trie[MAXN*20][2],sz[MAXN*20],cnt=1,chose[MAXN*20],dep[MAXN*20],p[MAXN],fa[MAXN*20];
ll ans;
il insert(int a,int k1){
ri pos=1;
for(ri i=31;i>=0;--i){
++sz[pos],dep[pos]=i;
ri k=((a&(1<<i))>0);
if(!Trie[pos][k]) Trie[pos][k]=++cnt;
fa[Trie[pos][k]]=pos;
pos=Trie[pos][k];
}
++sz[pos],dep[pos]=-1;
p[k1]=fa[pos];
}
il DFS(int p1,int p2,int &res,int pre){
if(dep[p1]==-1) return res=min(res,pre),void();
char fla=1;
if(sz[Trie[p1][0]]&&sz[Trie[p2][0]]) DFS(Trie[p1][0],Trie[p2][0],res,pre),fla=0;
if(sz[Trie[p1][1]]&&sz[Trie[p2][1]]) DFS(Trie[p1][1],Trie[p2][1],res,pre),fla=0;
if(sz[Trie[p1][0]]&&sz[Trie[p2][1]]&&fla) DFS(Trie[p1][0],Trie[p2][1],res,pre+(1ll<<dep[p1]));
if(sz[Trie[p1][1]]&&sz[Trie[p2][0]]&&fla) DFS(Trie[p1][1],Trie[p2][0],res,pre+(1ll<<dep[p1]));
}
il updata(int pos){
if(!sz[Trie[pos][0]]||!sz[Trie[pos][1]]) return ;
ri res=INT_MAX;
DFS(Trie[pos][0],Trie[pos][1],res,1ll<<dep[pos]);
chose[pos]=res;
ans+=res;
}
it LCA(int p1,int p2){
while(p1!=p2) p1=fa[p1],p2=fa[p2];
return p1;
}
int main(){
freopen("fight.in","r",stdin);
freopen("fight.out","w",stdout);
read(n),read(m);
for(ri i=1;i<=n;++i) read(val[i]);
for(ri i=1;i<=n;++i) insert(val[i],i);
for(ri i=1;i<=cnt;++i) updata(i);
print(ans),puts("");
for(ri i=1;i<=m;++i){
read(u),read(v),read(d);
d=(val[u]^val[v])&d;
ri lca=LCA(p[u],p[v]);
print(min(ans,ans-chose[lca]+d)),puts("");
}
return 0;
}

$T3$

大家都没看懂

总结

这次的题其实是可以拿到 $170$ 的,但是没有拿到,说明在考场上的思维还是不够活跃,要加油。