图论 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. python导入包失败ModuleNotFoundError: No module named 'matplotlib.pyplot'; 'matplotlib' is not a package

    最近在看 python,在使用matplotlib进行绘图时,提示:ModuleNotFoundError: No module named 'matplotlib.pyplot'; 'matplot ...

  2. PWM 定义

    简单的说,比如你有5V电源,要控制一台灯的亮度,有一个传统办法,就是串联一个可调电阻,改变电阻,灯的亮度就会改变.还有一个办法,就是PWM调节.不用串联电阻,而是串联一个开关.假设在1秒内,有0.5秒 ...

  3. Power-Aware GateSim Debug

    For PAG debug, the following steps may be useful. 1. Get correct netlists from PD which contain powe ...

  4. Netty 介绍和应用场景(一)

    1.为什么选择Netty 需要了解了Socket通信(IO/NIO/AIO)编程,对于通信模型已经有了一个基本的认识.,果想把这些真正的用于实际工作中,那么还需要不断的完善.扩展和优化.比如经典的TC ...

  5. java agent问题

    Error occurred during initialization of VMagent library failed to init: instrumentobjc[36987]: Class ...

  6. (57)Linux驱动开发之三Linux字符设备驱动

    1.一般情况下,对每一种设备驱动都会定义一个软件模块,这个工程模块包含.h和.c文件,前者定义该设备驱动的数据结构并声明外部函数,后者进行设备驱动的具体实现. 2.典型的无操作系统下的逻辑开发程序是: ...

  7. 南昌网络赛 H The Nth Item

    南昌网络赛The Nth Item 暴力快速幂+unordered_map记忆化 注意:记忆化不能写到快速幂求解函数里,不断调用函数会造成很大的时间浪费 #include<bits/stdc++ ...

  8. 南京网络赛C

    分段打表大法好!!! 打表40min,A题1s https://nanti.jisuanke.com/t/41300 #include<bits/stdc++.h> #define int ...

  9. Spring Data JPA(一)简介

    Spring Data JPA介绍 可以理解为JPA规范的再次封装抽象,底层还是使用了Hibernate的JPA技术实现,引用JPQL(Java Persistence Query Language) ...

  10. [BZOJ4010]:[HNOI2015]菜肴制作(拓扑排序)

    题目传送门 题目描述 知名美食家小A被邀请至ATM大酒店,为其品评菜肴. ATM酒店为小A准备了N道菜肴,酒店按照为菜肴预估的质量从高到低给予1到N的顺序编号,预估质量最高的菜肴编号为1.由于菜肴之间 ...