图论 test solution

T1:潜伏

题目背景

小悠回家之后,跟着母亲看了很多抗日神剧,其中不乏一些谍战片。

题目描述

解放前夕,北平城内潜伏着若干名地下党员,他们居住在城市的不同位置。现在身为地下党第一指挥官的你,想知道地下党员之间的最小通信距离,即从某一地下党员住处前往另一地下党员住处的距离的最小值。

我们可以将北平城视为一张N个点M条边的无向图,每条边连接两个点 ,且长度为\(w_i\)。

输入格式

每个测试点包含多组数据。

第一行,给出数据组数 ,之后依次输入每组数据。

每组数据的第一行,N,M,K,分别表示点数,边数,地下党员数。

之后M行,每\(u_i,v_i,w_i\)表示第i条边。

之后一行,K个整数代表地下党员所在结点。

结点编号为1到N,保证N>=K。

输出格式

对于每组数据,输出一行一个整数,表示地下党员之间的最小通信距离。

如果最小通信距离为∞,请输出-1代替。

样例输入

3
5 10 3
1 3 437
1 2 282
1 5 328
1 2 519
1 2 990
2 3 837
2 4 267
2 3 502
3 5 613
4 5 132
1 3 4
10 13 4
1 6 484
1 3 342
2 3 695
2 3 791
2 8 974
3 9 526
4 9 584
4 7 550
5 9 914
6 7 444
6 8 779
6 10 350
8 8 394
9 10 3 7
10 9 4
1 2 330
1 3 374
1 6 194
2 4 395
2 5 970
2 10 117
3 8 209
4 9 253
5 7 864
8 5 10 6

样例输出

437
526
641

数据范围

对于所有测试点,T<=10,时间限制2000ms,内存限制1GB 。

对于50%的测试点,N<=1000,M<=2000。

对于20%的测试点,N<=100000,输无的无向图无环。

对于30%的测试点,N<=100000,M<=200000。

所有 0<=\(w_i\)<=1000。

50pts:

进行k次单源最短路计算(用dijkstra比较好吧,堆优化的话复杂度可以降到O((n+m)logn)*k;

然后Floyd是不行的,会死的很惨。

以下是50pts程序

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<string>
#include<queue>
#define pr pair<int,int> using namespace std; const int mxn=100005;
const int mxm=200005; inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
} int n,m,k;
int head[mxn],ecnt;
struct edge{
int to,dis,nxt;
}e[mxm<<1]; void add(int from,int to,int dis){
++ecnt;
e[ecnt].to=to;
e[ecnt].dis=dis;
e[ecnt].nxt=head[from];
head[from]=ecnt;
} int dis[mxn];
int p[mxn];
bool bo[mxn],vis[mxn]; int dijkstra(int s){
priority_queue<pr,vector<pr>,greater<pr> > q;
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[s]=0;
q.push(make_pair(dis[s],s));
int ans=2147483647;
while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(dis[v]>dis[u]+e[i].dis){
dis[v]=dis[u]+e[i].dis;
if(bo[v]) ans=min(ans,dis[v]);
q.push(make_pair(dis[v],v));
}
}
}
return ans;
} int main(){
int T,u,v,w; T=read();
while(T--){
int ans=2147483647;
for(int i=1;i<=n;i++)head[i] =p[i]=bo[i]=0;
n=read();m=read();k=read();
for(int i=1;i<=m;i++){
u=read();v=read();w=read();
add(u,v,w);add(v,u,w);
}
for(int i=1;i<=k;i++){
p[i]=read();bo[p[i]]=1;
}
for(int i=1;i<=k;i++)
ans=min(ans,dijkstra(p[i]));
printf("%d\n",ans);
}
return 0;
}

对于20%的测试点,N<=100000,输无的无向图无环。

是森林,然后可以考虑入门级树形DP。

但是我不会

100pts:

优化枚举量

显然我们所求答案的两个点是不同的点,所以编号的二进制表示中至少一位不同

考虑枚举二进制每一位:

假设枚举到第i位,把这一位是1的设为源点,0的设为汇点,跑多源多汇最短路(怎么跑?)

(我们可以建一个超级源点,一个超级汇点,将所有第i位为1的都和超级源点连一条长度为0的边,所有第i位为0的点,向超级汇点连一条长度为0的点,那么我们跑多源多汇最短路就变成了求超级源点到超级汇点的最短路。)

多源多汇最短路的源点与终点的选取既可以从1n中选取,也可以从1k选取。

显然1~k更优一些

因为int只有32位,所以跑32次即可得到最优解;

#include <queue>
#include <cstdio>
#include <cstring> template <class cls>
inline cls min(const cls & a, const cls & b) {
return a < b ? a : b;
} const int mxn = 100005;
const int mxm = 500005;
const int inf = 0x3f3f3f3f; int n, m, k; int points[mxn]; int tot;
int hd[mxn];
int nt[mxm];
int to[mxm];
int vl[mxm]; inline void add_edge(int u, int v, int w) {
nt[++tot] = hd[u];
to[tot] = v;
vl[tot] = w;
hd[u] = tot;
} int dis[mxn]; struct data {
int u, d;
//u:特工原编号
//d: 由源点到终点的最短路径 data(int _u, int _d) :
u(_u), d(_d) {} bool operator < (const data & that) const {
return d > that.d;
}
}; std::priority_queue<data> heap; int main() {
int cas;
scanf("%d", &cas);
for (int c = 0; c < cas; ++c) {
scanf("%d%d%d", &n, &m, &k);
memset(hd, 0, sizeof(int) * (n + 5)); tot = 0;//大概是memset了n+5个位置来减少时间;
for (int i = 0, u, v, w; i < m; ++i) {
scanf("%d%d%d", &u, &v, &w);
add_edge(u, v, w);
add_edge(v, u, w);
}
for (int i = 0; i < k; ++i)
scanf("%d", points + i);//指针型写法
int ans = inf;
for (int i = 1; i < k; i <<= 1) { //枚举第i位是1
//以下本质是个dijkstra
memset(dis, inf, sizeof(int) * (n + 5));
for (int j = 0, p; j < k; ++j)
if (p = points[j], (j & i) == 0)//相当于对每个地下党从0~p-1重新编了个号
heap.push(data(p, dis[p] = 0));//判断这k个特工的第i位是0还是1
//这里dis[p]=0可以看做是从超级源点到p的距离为0
while (!heap.empty()) {
int u = heap.top().u;
int d = heap.top().d;
heap.pop();
if (dis[u] != d)//和判断vis是相同的
continue;
for (int e = hd[u], v, w; e; e = nt[e])
if (v = to[e], w = vl[e], dis[v] > d + w)
heap.push(data(v, dis[v] = d + w));
}
for (int j = 0, p; j < k; ++j)//然后求这其中最小的(可以看做是求到超级汇点的最短路)
if (p = points[j], (j & i) != 0)
ans = min(ans, dis[p]);
}
printf("%d\n", ans == inf ? -1 : ans);
}
return 0;
}

T2:神经

题目背景

神经网络在人工智能领域大火,所以小悠学习了些许关于神经的知识。

题目描述

神经网络可以表示为N个点M条边的有向图,每个结点带有一个兴奋权值\(w_i\)

如果以x单位大小的电流刺激结点u,设\(v_1,v_2,……,v_k\)是从u出发可以到达的结点,则神经网络会产生\(x×max\{w_{v_1},w_{v_2}……w_{v_k}\}\)的兴奋度,请注意,我们认为从u出发可以到达u。

现在请你回答若干询问,每个询问表示为:在以\(x_i\)大小的电流刺激\(u_i\)点后,网络的兴奋度是多少。

输入格式

每个测试点包含多组数据。

第一行,一个整数T表示数据组数,之后依次输入每组数据。

每组数据第一行,N,M,K分别表示点数,边数,询问次数。

之后一行,N个整数,\(w_1,w_2,…,w_N\)表示每个点的兴奋权值。

之后M行,每行\(u_i,v_i\)表示一条从\(u_i\)到\(v_i\)的单向边。

之后K行,每行\(u_i,x_i\)表示一次刺激。

输出格式

对于每个测试点中的每个询问,输出一行一个整数表示答案。

样例输入

3
5 10 5
4 10 8 1 10
1 3
1 4
1 5
1 3
2 1
2 5
4 3
4 3
4 5
5 1
1 4
4 6
1 9
4 7
2 9
5 10 5
2 8 8 10 10
2 1
2 3
3 2
3 4
3 1
3 2
3 4
4 1
5 4
5 1
1 4
2 3
4 7
3 10
1 5
5 10 5
9 9 8 2 1
1 5
1 5
2 1
2 4
2 4
2 4
3 2
3 1
4 3
4 3
5 9
3 9
2 7
5 1
5 4

样例输出

40
60
90
70
90
8
30
70
100
10
9
81
63
14

数据范围

对于所有数据,T<=5,时间限制 1000ms,内存限制1GB 。

对于50%的测试点,N<=1000,M<=2000,K<=1000。

对于20%的测试点,N<=100000,M<=200000,K<=100000,输入的有向图构成DAG。

对于30%的测试点,N<=200000,M<=400000,K<=100000。

所有 \(0<=x,w_i<=10^9\)。

50pts:

暴力bfs,注意要开long long

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<string>
#include<queue>
#define ll long long using namespace std; inline ll read(){
ll ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
const int mxn=200010;
const int mxm=400010;
ll n,m,k;
ll w[mxn];
ll cnt;
struct node{
ll to,nxt;
}e[mxm];
ll ecnt,head[mxn];
void add(ll from,ll to){
++ecnt;
e[ecnt].to=to;
e[ecnt].nxt=head[from];
head[from]=ecnt;
} ll maxn[mxn];
bool vis[mxn]; ll solve(ll s){
memset(vis,0,sizeof(vis));
queue<ll> q;
q.push(s);
vis[s]=1;
ll ans=w[s];
while(!q.empty()){
ll u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
ll v=e[i].to;
if(!vis[v]){
q.push(v);
vis[v]=1;
ans=max(ans,w[v]);
}
}
}
return ans;
} int main(){
ll T,u,v,x;
T=read();
while(T--){
for(int i=1;i<=n;i++)
w[i]=head[i]=0;
n=read();m=read();k=read();
for(int i=1;i<=n;i++) w[i]=read(); for(int i=1;i<=m;i++) {
u=read();v=read();
add(u,v);
}
for(int i=1;i<=k;i++){
u=read();x=read();
printf("%lld\n",solve(u)*x);
}
}
return 0;
}

2.对于20%的数据,是DAG,拓扑排序,dp。

尝试手写代码rwr:

(啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊写不对了)

神仙wz帮忙改,终于改对了(细节锅,awsl);

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<string>
#include<queue>
#define ll long long using namespace std; inline ll read(){
ll ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
const int mxn=200010;
const int mxm=400010;
ll n,m,k;
ll w[mxn];
ll cnt;
struct node{
ll to,nxt;
}e[mxm];
ll ecnt,head[mxn];
void add(ll from,ll to){
++ecnt;
e[ecnt].to=to;
e[ecnt].nxt=head[from];
head[from]=ecnt;
} ll in[mxn];
bool vis[mxn]; ll solve(ll s){
memset(vis,0,sizeof(vis));
queue<ll> q;
q.push(s);
vis[s]=1;
ll ans=w[s];
while(!q.empty()){
ll u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
ll v=e[i].to;
if(!vis[v]){
q.push(v);
vis[v]=1;
ans=max(ans,w[v]);
}
}
}
return ans;
}
ll mx[mxn]; void topu(){
memset(mx,0,sizeof(mx));
queue<int> q;
for(int i=1;i<=n;i++){
if(in[i]==0)
q.push(i);
mx[i]=w[i];
}
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
in[v]--;
mx[v]=max(mx[v],mx[u]);
if(in[v]==0) q.push(v);
}
}
} int main(){
ll T,u,v,x;
T=read();
while(T--){
ecnt=0;
for(int i=1;i<=n;i++)
w[i]=head[i]=in[i]=0;
n=read();m=read();k=read();
for(int i=1;i<=n;i++) w[i]=read();
for(int i=1;i<=m;i++) {
u=read();v=read();
if(n<=1000) add(u,v);
else {
add(v,u);
in[u]++;
}
}
if(n>1000) topu();
for(int i=1;i<=k;i++){
u=read();x=read();
if(n<=1000) printf("%lld\n",solve(u)*x);
else
printf("%lld\n",mx[u]*x);
}
}
return 0;
}

3.tarjan缩点,变成DAG,然后再拓扑排序dp

然后不知道为什么,lemon把std和自己的代码都卡死了

std:

#include <cstdio>
#include <cstring> template <class cls>
inline cls min(const cls & a, const cls & b) {
return a < b ? a : b;
} template <class cls>
inline cls max(const cls & a, const cls & b) {
return a > b ? a : b;
} const int mxn = 200005;
const int mxm = 400005; int n, m, k, w[mxn]; struct edge {
int u, v;
} edges[mxm]; int tot;
int hd[mxn];
int to[mxm << 1];
int nt[mxm << 1]; inline void add_edge(int u, int v) {
nt[++tot] = hd[u];
to[tot] = v;
hd[u] = tot;
} int tim;
int cnt;
int top;
int dfn[mxn];
int low[mxn];
int stk[mxn];
int scc[mxn]; void tarjan(int u) {
dfn[u] = low[u] = ++tim; stk[++top] = u;
for (int e = hd[u], v; e; e = nt[e])
if (v = to[e], scc[v] == 0) {
if (dfn[v] == 0)tarjan(v),
low[u] = min(low[u], low[v]);
else
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
cnt += 1;
do {
scc[stk[top]] = cnt;
} while (stk[top--] != u);
}
} int oe[mxn];
int mx[mxn]; int que[mxn]; void bfs() {
int l = 0, r = 0;
for (int i = 1; i <= cnt; ++i)
if (oe[i] == 0)
que[r++] = i;
while (l < r) {
int u = que[l++];
for (int e = hd[u], v; e; e = nt[e])
if (v = to[e], mx[v] = max(mx[v], mx[u]), --oe[v] == 0)
que[r++] = v;
}
} int main() {
int cas;
scanf("%d", &cas);
for (int c = 0; c < cas; ++c) {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; ++i)
scanf("%d", w + i);
memset(hd, 0, sizeof(int) * (n + 5)); tot = 0;
for (int i = 0; i < m; ++i) {
scanf("%d%d", &edges[i].u, &edges[i].v);
add_edge(edges[i].u, edges[i].v);
}
tim = cnt = top = 0;
memset(scc, 0, sizeof(int) * (n + 5));
memset(dfn, 0, sizeof(int) * (n + 5));
for (int i = 1; i <= n; ++i)
if (scc[i] == 0)
tarjan(i);
memset(hd, 0, sizeof(int) * (cnt + 5)); tot = 0;
memset(oe, 0, sizeof(int) * (cnt + 5));
memset(mx, 0, sizeof(int) * (cnt + 5));
for (int i = 0; i < m; ++i) {
int u = scc[edges[i].u];
int v = scc[edges[i].v];
if (u != v)
add_edge(v, u), oe[u] += 1;
}
for (int i = 1; i <= n; ++i)
mx[scc[i]] = max(mx[scc[i]], w[i]);
bfs();
for (int i = 0, u, x; i < k; ++i) {
scanf("%d%d", &u, &x);
printf("%lld\n", 1LL * x * mx[scc[u]]);
}
}
return 0;
}

lz's:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<string>
#include<queue>
#define ll long long using namespace std; inline ll read(){
ll ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
const int mxn=200010;
const int mxm=400010;
ll n,m,k;
ll w[mxn];
ll cnt;
struct node{
ll to,nxt;
}e[mxm];
ll ecnt,head[mxn];
void add(ll from,ll to){
++ecnt;
e[ecnt].to=to;
e[ecnt].nxt=head[from];
head[from]=ecnt;
} ll in[mxn],u[mxm],v[mxm];
bool vis[mxn]; int dfn[mxn],low[mxn],stk[mxn<<1];
int tim,top;
int scc[mxn]; void tarjan(int u) {
dfn[u]=low[u]=++tim;
stk[++top]=u;
vis[u]=1;
for (int i=head[u],v;i;i=e[i].nxt){
v=e[i].to;
if (!dfn[v]) {
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]) low[u]=min(low[u],dfn[v]);
}
if (dfn[u]==low[u]) {
++cnt;
do {
scc[stk[top]]=cnt;
vis[stk[top]]=0;
} while (stk[top--]!=u);
}
} ll mx[mxn]; void topu(){
queue<int> q;
for(int i=1;i<=cnt;i++){
if(in[i]==0)
q.push(i);
}
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
in[v]--;
mx[v]=max(mx[v],mx[u]);
if(in[v]==0) q.push(v);
}
}
} void clean(){
for(int i=1;i<=n+5;i++)
w[i]=head[i]=in[i]=mx[i]=0;
for(int i=1;i<=m+5;i++)
v[i]=u[i]=0;
ecnt=0;
} void vclean(){
top=tim=ecnt=0;
memset(dfn,0,sizeof(int)*(n+5));
memset(vis,0,sizeof(int)*(n+5));
memset(stk,0,sizeof(int)*(n+5));
} int main(){
ll T,u_,x_;
T=read();
while(T--){
n=read();m=read();k=read();
clean();
for(int i=1;i<=n;i++) w[i]=read();
for(int i=1;i<=m;i++) {
u[i]=read();v[i]=read();
add(u[i],v[i]);
}
vclean();
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)
mx[scc[i]]=max(mx[scc[i]],w[i]);
memset(head,0,sizeof(head));
for(int i=1;i<=m;i++){
if(scc[u[i]]!=scc[v[i]]){
add(scc[v[i]],scc[u[i]]);
in[scc[u[i]]]++;
}
}
// cout<<cnt<<endl;
topu();
for(int i=1;i<=k;i++){
u_=read();x_=read();
printf("%lld\n",mx[scc[u_]]*x_);
}
}
return 0;
}

rqy的神仙做法:读入所有点点权,按点权从大到小排序,反向建图,从权值最大的点出发,覆盖点,然后再从第2大的点出发

T3:计数

题目背景

小悠的导师对于树叶颜色有很深的研究,但是碍于眼神不好,总是要请小悠来帮忙数一数树上某种颜色的叶子有多少片。

题目描述

给出一棵N个结点的树,每个结点初始具有一个颜色\(c_i\),现在有如下两种操作——

  1. 更改结点u的颜色为x。
  2. 询问从u到v的路径上有多少个颜色为x的结点。

现在请你按顺序完成若干次操作。

输入格式

每个测试点包含多组数据。

第一行,一个整数T表示数据组数,之后依次输入每组数据。

每组数据第一行,N,M分别表示点数、操作次数。

之后一行,N个整数\(c_1,c_2,……,c_N\)表示每个点的初始颜色。

之后N-1行,每行两个整数,\(u_i,v_i\),表示树上存在一条连接\(u_i,v_i\)的边。

之后M行,表示每个操作。

如果是操作1,则形如1 u x。

如果是操作2,则形如2 u v x。

输出格式

对于每组数据的每个操作2,输出一行一个整数表示答案。

样例输入

3
5 5
3 2 1 1 1
1 2
2 3
2 5
3 4
2 3 4 1
1 2 1
2 3 5 1
2 1 5 3
2 4 4 3
5 5
1 2 1 2 2
1 2
2 3
2 4
3 5
1 1 2
1 3 2
1 2 2
2 4 2 2
2 1 4 1
5 5
2 1 1 1 1
1 2
1 4
2 3
4 5
2 4 2 1
1 1 1
2 1 4 1
2 3 3 1
2 2 2 2

样例输出

2
3
1
0
2
0
2
2
1
0

数据范围

对于所有测试点,T<=5,时间限制1000ms,内存限制1GB。

对于50%的测试点,N<=1000,M<=1000。

对于20%的测试点,N<=100000,M<=100000,颜色种类数不超过5。

对于30%的测试点,N<=100000,M<=100000。

所有测试点颜色种类数均不超过100。

对于前50%的数据:每次询问bfs一遍:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<string>
#include<queue> using namespace std; inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
} const int mxn=100010;
const int mxm=200010; struct node{
int to,nxt;
}e[mxm];
int ecnt,head[mxn];
void add(int from,int to){
++ecnt;
e[ecnt].to=to;
e[ecnt].nxt=head[from];
head[from]=ecnt;
}
struct Node{
int cnt,u;
};
Node _n(int cnt,int u){
Node rtn;
rtn.cnt=cnt;
rtn.u=u;
return rtn;
} bool vis[mxn];
int c[mxn],n,m,u,v,x; int solve(int u,int k,int x){
memset(vis,0,sizeof(vis));
queue<Node> q;
if(c[u]==x) q.push({1,u});
else q.push({0,u});
vis[u]=1;
while(!q.empty()){
Node z=q.front();
q.pop();
for(int i=head[z.u];i;i=e[i].nxt){
int v=e[i].to;
if(v==k){
if(c[v]==x) return z.cnt+1;
else return z.cnt;
}
if(!vis[v]){
vis[v]=1;
if(c[v]==x) q.push(_n(z.cnt+1,v));
else q.push({z.cnt,v});
}
}
}
} int main(){
// freopen("count.in","r",stdin);
// freopen("count.out","w",stdout);
int T,opt;
T=read();
while(T--){
for(int i=1;i<=n;i++)
head[i]=c[i]=0;
n=read();
m=read();
for(int i=1;i<=n;i++)
c[i]=read();
for(int i=1;i<n;i++){
u=read();v=read();
add(u,v);
add(v,u);
}
for(int i=1;i<=m;i++){
opt=read();
if(opt==1){
u=read();x=read();
c[u]=x;
}
else {
u=read();v=read();x=read();
if(u==v){
if(c[u]==x)
printf("1\n");
else printf("0\n");
}
else
printf("%d\n",solve(u,v,x));
}
}
}
return 0;
}

对于剩下20%的数据,树链剖分,开五棵线段树,颜色为1的就在第一棵线段树的位置+1,颜色由1到2,2+1,1-1;

对于100%的数据,暴力:开100个树状数组,和刚才没什么区别

如果线段树在每一个节点上维护一个100的数组

合并的时候可以直接暴力统计节点次数,这样代价是区间长度

如果每一位枚举则是n*100

每一层访问的点是n的,一共log层

o(nlogn)

离线操作

-1和+1分别隶属于x和y棵线段树

把操作分类,每一次处理每一棵的线段树

有多少个颜色就有多少棵

所有操作次数相加就是2m

所以操作还是o(m)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm> using namespace std; inline int getint()
{
int r = 0, c = getchar(); for (; c < 48; c = getchar());
for (; c > 47; c = getchar())
r = r * 10 + c - 48; return r;
} const int mxc = 100005;
const int mxn = 100005;
const int mxm = 200005; int n, m, c; int tt;
int hd[mxn];
int to[mxm];
int nt[mxm]; inline void addedge(int x, int y)
{
nt[++tt] = hd[x], to[tt] = y, hd[x] = tt;
nt[++tt] = hd[y], to[tt] = x, hd[y] = tt;
} struct data
{
int k, x, y; data() {} ;
data(int a, int b, int c)
: k(a), x(b), y(c) {} ;
}; int color[mxn]; #include <vector> vector<data> vec[mxc]; int tim;
int dfn[mxn];
int top[mxn];
int fat[mxn];
int dep[mxn];
int son[mxn];
int siz[mxn]; void dfs1(int u, int f)
{
siz[u] = 1;
son[u] = 0;
fat[u] = f;
dep[u] = dep[f] + 1; for (int i = hd[u], v; i; i = nt[i])
if (v = to[i], v != f)
{
dfs1(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]])
son[u] = v;
}
} void dfs2(int u, int f)
{
dfn[u] = ++tim; if (son[f] == u)
top[u] = top[f];
else
top[u] = u; if (son[u])
dfs2(son[u], u); for (int i = hd[u], v; i; i = nt[i])
if (v = to[i], v != f && v != son[u])
dfs2(v, u);
} int bit[mxn]; inline void add(int p, int v)
{
for (; p <= n; p += p & -p)
bit[p] += v;
} inline int ask(int l, int r)
{
int sum = 0; --l; for (; r; r -= r & -r)
sum += bit[r]; for (; l; l -= l & -l)
sum -= bit[l]; return sum;
} int ans[mxn]; signed main()
{
int cas = getint(); while (cas--)
{
n = getint();
m = getint(); for (int i = 1; i <= n; ++i)
vec[color[i] = getint()].push_back(data(0, i, +1)); c = 0; for (int i = 1; i <= n; ++i)
c = max(c, color[i]); memset(hd, 0, sizeof(int) * (n + 5)); tt = 0; for (int i = 1; i < n; ++i)
{
int x = getint();
int y = getint(); addedge(x, y);
} for (int i = 1; i <= m; ++i)
{
if (getint() == 1)
{
int p = getint();
int a = color[p];
int b = color[p] = getint(); vec[a].push_back(data(0, p, -1));
vec[b].push_back(data(0, p, +1));
}
else
{
int x = getint();
int y = getint();
int k = getint(); vec[k].push_back(data(i, x, y));
}
} dfs1(1, 0);
dfs2(1, 0); memset(ans, -1, sizeof ans); for (int k = 1; k <= c; ++k)
{
int sz = vec[k].size(); memset(bit, 0, sizeof bit); for (int i = 0; i < sz; ++i)
{
const data &d = vec[k][i]; ans[d.k] = 0; if (d.k == 0)
add(dfn[d.x], d.y);
else
{
int a = d.x, ta = top[a];
int b = d.y, tb = top[b]; while (ta != tb)
{
if (dep[ta] >= dep[tb])
ans[d.k] += ask(dfn[ta], dfn[a]), ta = top[a = fat[ta]];
else
ans[d.k] += ask(dfn[tb], dfn[b]), tb = top[b = fat[tb]];
} if (dep[a] <= dep[b])
ans[d.k] += ask(dfn[a], dfn[b]);
else
ans[d.k] += ask(dfn[b], dfn[a]);
}
}
} for (int i = 1; i <= m; ++i)
if (ans[i] >= 0)
printf("%d\n", ans[i]); for (int i = 1; i <= c; ++i)
vec[i].clear(); tim = 0;
} return 0;
}

图论 test solution的更多相关文章

  1. [leetcode] 题型整理之图论

    图论的常见题目有两类,一类是求两点间最短距离,另一类是拓扑排序,两种写起来都很烦. 求最短路径: 127. Word Ladder Given two words (beginWord and end ...

  2. Codeforces 553C Love Triangles(图论)

    Solution: 比较好的图论的题. 要做这一题,首先要分析love关系和hate关系中,love关系具有传递性.更关键的一点,hate关系是不能成奇环的. 看到没有奇环很自然想到二分图的特性. 那 ...

  3. 图论+dp poj 1112 Team Them Up!

    题目链接: http://poj.org/problem?id=1112 题目大意: 有编号为1~n的n个人,给出每个人认识的人的编号,注意A认识B,B不一定认识A,让你将所有的人分成两组,要求每组的 ...

  4. [题解] [NOIP2008] 双栈排序——关系的冲突至图论解法

    Problem 题目描述 Tom最近在研究一个有趣的排序问题.如图所示,通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入序列升序排序. 操作a 如果输入序列不为空,将第一个元素压入栈S1 操 ...

  5. 【题解】Points, Lines and Ready-made Titles Codeforces 871C 图论

    Prelude 真是一道好题,然而比赛的时候花了太多时间在B题上,没时间想这个了QAQ. 题目链接:萌萌哒传送门(.^▽^) Solution 观察样例和样例解释,我们发现,假如有四个点,恰好占据在某 ...

  6. 算法笔记_149:图论之桥的应用(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 1310 One-way traffic In a certain town there are n intersections connected ...

  7. Solution: 最近公共祖先·一 [hiho一下 第十三周]

    题目1 : 最近公共祖先·一 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Ho最近发现了一个神奇的网站!虽然还不够像58同城那样神奇,但这个网站仍然让小Ho乐在其中 ...

  8. SC3聚类 | 拉普拉斯矩阵 | Laplacian matrix | 图论 | R代码

    Laplacian和PCA貌似是同一种性质的方法,坐标系变换.只是拉普拉斯属于图论的范畴,术语更加专业了. 要看就把一篇文章看完整,再看其中有什么值得借鉴的,总结归纳理解后的东西才是属于你的. 问题: ...

  9. DP&图论 DAY 7 上午

    DP&图论  DAY 7  上午 图论练习题 P2176 [USACO14FEB]路障Roadblock 先跑最短路(最多n条边,否则出环) 枚举每条边,加倍,再跑 dijkstra 取最大 ...

随机推荐

  1. JAVA笔记15-线程同步

    一.概念 1.多个线程之间访问同一资源,进行协调的过程是线程同步.例如两个人同时操作同一银行账户.解决方法:加锁 2.Java种引入对象互斥锁的概念,保证共享数据操作的完整性.每个对象都对应于一个可称 ...

  2. jquery gt选择器 语法

    jquery gt选择器 语法 作用::gt 选择器选取 index 值高于指定数的元素.index 值从 0 开始.经常与其他元素/选择器一起使用,来选择指定的组中特定序号之后的元素(如上面的例子) ...

  3. C++ -- 类与成员

    一.初始化列表 1.是构造函数中一种成员的初始化方式   例如,class    类名 { 类名(参数列表):成员1(成员1),成员2(成员2)... {   } } 2.用此方法可以解决类中的成员与 ...

  4. python基础之 数据格式化

    %还是format 皇城PK Python中格式化字符串目前有两种阵营:%和format,我们应该选择哪种呢? 自从Python2.6引入了format这个格式化字符串的方法之后,我认为%还是form ...

  5. 初始化String,System,OutOfMemoryError

    对于String, System而言,其初始化过程和 Thread一致.而对于OutOfMemoryError而言,其继承结构如下: 因此对于OutOfMemoryError,会首先将状态改为4,然后 ...

  6. MySQL中concat以及group_concat的使用

    摘自:https://www.jianshu.com/p/43cb4c5d33c1 说明: 本文中使用的例子均在下面的数据库表tt2下执行: 一.concat()函数 1.功能:将多个字符串连接成一个 ...

  7. __proto__ VS. prototype in JavaScript

    __proto__ VS. prototype in JavaScript http://dmitrysoshnikov.com/ecmascript/javascript-the-core/#a-p ...

  8. LeetCode_21.合并两个有序链表

    LeetCode_21 LeetCode-21.合并两个有序链表 将两个有序链表合并为一个新的有序链表并返回. 新链表是通过拼接给定的两个链表的所有节点组成的. 示例: 输入:1->2-> ...

  9. 【c++进阶:c++ 顺序容器vector,string,deque,list,forward_list,array常用性质】

    常用5种顺序容器性质: https://blog.csdn.net/oil_you/article/details/82821833 关于deque https://www.cnblogs.com/L ...

  10. 测开之路一百三十九:会话管理之cookie写入、读取、和更新

    机制:服务器端发送的小段文本信息存储在客户端硬盘 功能:记录用户偏好,请求.页面.站点间共享信息 特点:易丢失.安全隐患 添加cookie,需要用到make_respons.set_cookie @a ...