zoukankan      html  css  js  c++  java
  • Codeforces Educational Round 92 赛后解题报告(AG)

    Codeforces Educational Round 92 赛后解题报告

    惨 huayucaiji 惨

    A. LCM Problem

    赛前:A题嘛,总归简单的咯
    赛后:A题这种**题居然想了20min

    我说这句话,肯定是有原因的,我们看到 \(\operatorname{lcm}\) 会想到之前一些题:题目,我的题解

    自然就往 \(a\times b=\operatorname{gcd}(a,b)\times \operatorname{lcm}(a,b)\) 的方向思考,但是,这太麻烦了,我们可以这样想一个问题:

    如果我们现在有两个数 \(x,y\)。其中 \(x<y\)。那么我们一定有:

    \[\operatorname{lcm}(x,y)\geq 2x \]

    我们可以简略地证明一下,首先因为 \(x<y\),所以他们的 \(\operatorname{lcm}\) 一定大于 \(x\)。其次我们令 \(g=\operatorname{gcd}(x,y),x=q\cdot g,y=p\cdot g\)。所以 \(\operatorname{gcd}(p,q)=1\),即 \(p,q\) 互质,那么我们知道 \(\operatorname{lcm}(x,y)=g\cdot p\cdot q=x\cdot p\)。因为 \(p\) 是整数,又因为 \(x\neq y\)。所以 \(p\) 最小取 \(2\)。故 \(\operatorname{lcm}(x,y)\geq 2x\)

    我们只需要判断是否 \(2\times l\leq r\) 即可

    //Don't act like a loser.
    //You can only use the sode for studying or finding mistakes
    //Or,you'll be punished by Sakyamuni!!!
    #include<bits/stdc++.h>
    using namespace std;
    
    int read() {
    	char ch=getchar();
    	int f=1,x=0;
    	while(ch<'0'||ch>'9') {
    		if(ch=='-')
    			f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9') {
    		x=x*10+ch-'0';
    		ch=getchar();
    	}
    	return f*x;
    }
    int l,r,t;
    
    signed main() {
    	
    	t=read();
    	while(t--) {
    		l=read();r=read();
    		bool flag=0;
    		
    		if(l*2<=r) {
    			printf("%d %d\n",l,l*2);
    		}
    		else {
    			printf("-1 -1\n");
    		}
    		
    	}
    	return 0;
    }
    
    

    B. Array Walk

    我们先来分析题目中一个重要的条件:

    Also, you can't perform two or more moves to the left in a row.

    我们不能一次向左移动两格。所以这代表一次左后必定是右。那么我们很容易可以想到一个贪心的策略,我们无论如何,先走到 \(k+1\)。我们再来反悔,由于 \(z\) 很小,我们可以枚举往后走几步,并且维护前面相邻两个数的和的最大值,选择在这里后退前进。于是我们可以得到下面的程序:

    //Don't act like a loser.
    //You can only use the sode for studying or finding mistakes
    //Or,you'll be punished by Sakyamuni!!!
    #include<bits/stdc++.h>
    using namespace std;
    
    int read() {
    	char ch=getchar();
    	int f=1,x=0;
    	while(ch<'0'||ch>'9') {
    		if(ch=='-')
    			f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9') {
    		x=x*10+ch-'0';
    		ch=getchar();
    	}
    	return f*x;
    }
    
    const int maxn=1e5+10;
    
    int n,a[maxn],sum[maxn],maxx[maxn],k,z;
    
    signed main() {
    	int t=read();
    	while(t--) {
    		n=read();k=read();z=read();
    		for(int i=1;i<=n;i++) {
    			a[i]=read();
    			sum[i]=sum[i-1]+a[i];
    		}
    		fill(maxx,maxx+n+1,0);
    		
    		for(int i=1;i<=n;i++) {
    			maxx[i]=max(maxx[i-1],a[i]+a[i-1]);
    		}
    		int ans=sum[k+1];
    		for(int i=1;i<=z;i++) {
    			if(k+1-2*i>=2) {
    				ans=max(ans,maxx[k+1-2*i]*i+sum[k+1-2*i]);
    			}
    		}
    		
    		printf("%d\n",ans);
    	}
    	return 0;
    }
    
    

    呵呵,WA。

    我们再来仔细想想比如对于:

    1
    5 4 2
    50 40 30 20 10
    

    我们的最优方案是 \(50->40->50->40->50\) 答案为 \(230\)。但是我们上面的程序给出的答案是 \(50->40->50->40->30\)。答案为 \(210\)。为什么呢?原因就是我们珂以退回去后就不行再往前走了,留在后面的格子里。所以我们还要加上这一段代码:

    if(k+2-2*i>=2) {
    	ans=max(ans,sum[k+1-2*i]+i*(a[k-i*2+1]+a[k-i*2+2]));
    }
    

    注意一些下标的问题哦~

    C. Good String

    这个题比B简单吧

    我们可以通过题目得出以下结论:

    \[s_2\ \ s_3\ \ s_4.....s_{n-1}\ \ s_n\ \ s_1\\=s_n\ \ s_1\ \ s_2.....s_{n-3}\ \ s_{n-2}\ \ s_{n-1} \]

    所以我们有 \(s_1=s_3=s_5=....s_{n-1}=q\)\(s_2=s_4=s_6=.....s_{n}=p\)

    我们只需要枚举 \(p,q\) 即可,每次枚举出来后,顺次统计以下最少要删多少字符,用贪心一个一个扫就是最优的,这里就不再证明了。但是我们还有一些特殊情况要处理。

    如果我们最后删减版序列是 \(pqpqp\)。那么我们得出的两序列是 \(ppqpq\)\(qpqpp\)。显然在 \(p\neq q\) 时不等。所以当 \(p\neq q\)\(len\) 是奇数时,删去的数还要在加一。

    注意特判 \(len=1\)

    //Don't act like a loser.
    //You can only use the sode for studying or finding mistakes
    //Or,you'll be punished by Sakyamuni!!!
    #include<bits/stdc++.h>
    using namespace std;
    
    int read() {
    	char ch=getchar();
    	int f=1,x=0;
    	while(ch<'0'||ch>'9') {
    		if(ch=='-')
    			f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9') {
    		x=x*10+ch-'0';
    		ch=getchar();
    	}
    	return f*x;
    }
    
    int n;
    string s;
    
    signed main() {
    	
    	int t=read();
    	while(t--) {
    		cin>>s;
    		n=s.size();
    		
    		if(n==1) {
    			cout<<0<<endl;//特判 n=1
    			continue;
    		}
    		
    		int ans=n;
    		for(int i=0;i<=9;i++) {
    			for(int j=0;j<=9;j++) {
    				int num=0,sum=0;//num记录当前应该匹配哪个数
    				
    				for(int k=0;k<n;k++) {
    					if(!num) {
    						if(s[k]-'0'!=i) {
    							sum++;
    						}
    						else {
    							num=1-num;
    						}
    					}//分类
    					else {
    						if(s[k]-'0'!=j) {
    							sum++;
    						}
    						else {
    							num=1-num;
    						}
    					}
    				}
    				if(i!=j&&num) {
    					sum++;//删去的数+1
    				}
    				ans=min(ans,sum);
    			}
    		}
    		
    		printf("%d\n",ans);
    	}
    	return 0;
    }
    

    D. Segment Intersections

    我们先打开单词小本本记录一下 intersection 这个词(虽然我会)。它的意思是交叉,十字路口。

    我们切入正题。

    首先我们会发现问题分成两个类别。第一类是 \([l_1,r_1],[l_2,r_2]\) 有交集。即 \(\min(r_1,r_2)-\max(l_1,l_2)\geq 0\)。交集的长度(即题目中的 \(\operatorname{intersection\ length}\))也就为 \(\min(r_1,r_2)-\max(l_1,l_2)\)。如下图:

    那么此时我们选择这种做法(也就是官方解法)。我们每次将 \([l_1,r_1]\) 扩展为 \([l_1,r_1+1]\),或者同理将 \([l_2,r_2]\) 扩展为 \([l_2-1,r_2]\),知道两个区间完全相同为止,在这样扩充时,我们每用一步,就可以让交集长度增加 \(1\)。是最划算的。但是一旦两个区间完全相同,即 \(l_1=l_2,r_1=r_2\) 时,我们不能在这样操作了,我们必须让两个区间的同一边长度都增加 \(1\),才能让交集长度增加 \(1\)。这是唯一的选择了。那么第一类我们讨论完了。

    再来看第二类,也就是一开始没有交集。那么我们可以让它有交集在用第一种情况的处理方法来处理。我们要枚举一个 \(cnt\),代表要让多少对区间有交集。我们完全没有必要让所有的区间成双成对地有交集,毕竟有的用不上嘛~。我们让一堆区间有交集的最小步数就为 \(\max(l_1,l_2)-\min(r_1,r_2)\)。注意还要乘以 \(cnt\)。最后用第一种情况的处理方法来处理即可。时间复杂度为 \(O(n)\)

    注意细节!比如开 long long

    //Don't act like a loser.
    //You can only use the code for studying or finding mistakes
    //Or,you'll be punished by Sakyamuni!!!
    #include<bits/stdc++.h>
    #define int long long//不开long long 见祖宗!
    using namespace std;
    
    int read() {
    	char ch=getchar();
    	int f=1,x=0;
    	while(ch<'0'||ch>'9') {
    		if(ch=='-')
    			f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9') {
    		x=x*10+ch-'0';
    		ch=getchar();
    	}
    	return f*x;
    }
    
    int calc(int n,int k,int l1,int r1,int l2,int r2) {
    	if(k<=0) {
    		return 0;
    	}
    	
    	if((max(r1,r2)-min(l1,l2))*n>=k+(min(r1,r2)-max(l1,l2))*n)//注意细节 {
    		return k;
    	}
    	else {
    		int tmp=(max(l1,l2)-min(l1,l2)+max(r1,r2)-min(r1,r2))*n;//细节
    		return tmp+(k-tmp)*2;
    	}
    }
    
    signed main() {
    	int t=read();
    	
    	while(t--) {
    		int n,k,l1,l2,r1,r2;
    		n=k=l1=l2=r1=r2=0;
    		cin>>n>>k>>l1>>r1>>l2>>r2;
    		
    		int ans=0x7fffffffffffffff;
    		if(min(r1,r2)<max(l1,l2)) {
    			for(int i=1;i<=n;i++) {
    				ans=min(ans,(max(l1,l2)-min(r1,r2))*i+calc(i,k,min(l1,l2),min(r1,r2),min(r1,r2),max(r1,r2)));//细节,我们要进行计算的是我们处理完的区间,是有交集的,保证有交集即可。
    			}
    		}
    		else {
    			ans=calc(n,k-(min(r1,r2)-max(l1,l2))*n,l1,r1,l2,r2);
    		}
    		
    		printf("%lld\n",ans);//lld的细节
    	}
    	return 0;
    }
    

    E. Calendar Ambiguity

    我们会发现这个题目只有一个可以入手的点,其他都是定义类条件。

    A pair (x,y) such that x<y is ambiguous if day x of month y is the same day of the week as day y of month x.

    用数学方法表示也就是

    \[xd+y\equiv yd+x\ (\operatorname{mod}\ w)\\xd+y-(yd+x)\equiv 0\ (\operatorname{mod}\ w)\\(d-1)(x-y)\equiv 0\ (\operatorname{mod}\ w) \]

    \(d-1\) 是一个定值。我们只需要关注 \(x-y\)。但是注意了,我们的 \(d-1\)\(w\) 可能有相同的质因子,这些就没有必要再在 \(x-y\) 包含了。所以我们令 \(w'=\frac{w}{\gcd(d-1,w)}\)。我们要求的就是有多少对 \((x,y)\) 满足 \(x-y\equiv 0\ (\operatorname{mod}\ w')\)

    为了求解这个问题,我们可以考虑弱化条件。假设我们现在求解有多少对 \((x,y)\) 满足 \(x-y=k\cdot w'\)。那么我们可以用数学方法来解决,就有 \(\min(m,d)-k\cdot w'\) 对。这个应该十分好理解。 我们再把条件还原,我们就可以枚举 \(k\),在累加答案。由于 \(x-y\leq \min(m,d)\)所以最后的答案就是:

    \[\sum\limits_{k=1}^{\lfloor\frac{\min(m,d)}{w'}\rfloor} \min(m,d)-k\cdot w' \]

    聪明的小伙伴可能会发现,这样的时间复杂度实在太大,到了 \(O(t\cdot\lfloor\frac{\min(m,d)}{w'}\rfloor)\) 应该是过不了的,大家可以试一试。那么我们就要想如何优化。我们再次观察这个式子,发现是一个等差数列,公差是 \(-w'\)。我们用高斯公式就可以解决这个问题。首项为 \(\min(d,m)-w'\),末项为 \(\min(d,m)-\lfloor\frac{\min(m,d)}{w'}\rfloor\cdot w'\),项数为 \(\lfloor\frac{\min(m,d)}{w'}\rfloor\)。所以最终答案为:

    \[\frac{(2\min(d,m)-\lfloor\frac{\min(m,d)}{w'}\rfloor\cdot w'-w')\times\lfloor\frac{\min(m,d)}{w'}\rfloor}2 \]

    这样时间复杂度为 \(O(\log(m+d))\)

    时间复杂度的大户竟是计算 \(\gcd\) 你敢相信?

    //Don't act like a loser.
    //You can only use the code for studying or finding mistakes
    //Or,you'll be punished by Sakyamuni!!!
    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    
    int read() {
    	char ch=getchar();
    	int f=1,x=0;
    	while(ch<'0'||ch>'9') {
    		if(ch=='-')
    			f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9') {
    		x=x*10+ch-'0';
    		ch=getchar();
    	}
    	return f*x;
    }
    
    int m,d,w;
    
    int gcd(int x,int y) {
    	return y==0? x:gcd(y,x%y);
    }
    
    signed main() {
    	int t=read();
    	while(t--) {
    		m=read();d=read();w=read();
    		
    		w=w/gcd(w,d-1);
    		int n=min(m,d);
    		int num=n/w;
    		int ans=(2*n-w*num-w)*num/2;
    		
    		cout<<ans<<endl; 
    	}
    	return 0;
    }
    
    

    F. Bicolored Segments

    拿出单词小本本,我们再来记一个单词:
    embed:嵌入,本题中意思就是一个区间被另一个区间所包含。

    回归正题,我在写这篇题解时看了洛谷上一篇和我思路差不多的大佬写的题解,他的线段树优化DP写的不错的,大家可以看看,但是我觉得他的解法二不太清晰,特别是那个“对抗抵消”。可能蒟蒻不太能理解qwq,这里换一个角度在重新讲一下。

    我们这样想,既然有两种不同颜色的区间,就代表我们可以理解把它们为一个二分图的节点。如果你愿意,我们再把所有互为“不好的”区间连边。我们就得到了一个看着不错二分图。举个例子,我们题目中的样例二构图:

    样例二

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

    我们构成的二分图

    F

    我们再看这个图,是不是有一点感觉了,我们要求的正是这个图的最大点独立集。求最大点独立集,我们就要求最大匹配。这两个概念不知道的同学可以上网搜。我们令最大点独立集的答案为 \(x\),最大匹配的答案为 \(y\)。我们有 \(x+y=n\)\(n\) 就是点的个数。证明略我们的问题成功转化为这个二分图求最大匹配。

    我相信不会有人想着真的把图建出来用网络流求解吧,不会吧,不会吧。
    咳咳,我一开始就这么想的

    不扯远了,但确实我们的第一反应应该是网络瘤,毕竟是它的好伙伴二分图嘛。但是我们建图的时间复杂度就是 \(O(n^2)\),Dinic 做二分图的时间复杂度 \(O(m\sqrt{n})\),显然爆炸。我们要像一种高效的方法才行。

    我们先和上面的那篇 blog 一样,按每个区间的右端点为第一关键字排序,搞两个 multiset 记录我们选择了哪些区间。接下来的操作还是一样,但是我们的解释不太一样。我们要求的是最大匹配,如果我们在异色的 multiset 中,存在与我们现在考虑的这个区间形成“不好区间”的区间的话,我们就选择这两个点之间的这条边作为最大匹配中的一条边,把那个 muliset 中的点给抹掉,并且现在的这个区间也不加入到 multiset 中,因为我们这两个点不能再出现在最大匹配的边里了,这是最大匹配的定义要求的。否则这个区间我们可以加入到 multiset 中。这样解释应该更清楚一些吧(自认为)。也就不用去理解“对抗抵消”了。

    //Don't act like a loser.
    //You can only use the code for studying or finding mistakes
    //Or,you'll be punished by Sakyamuni!!!
    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    
    int read() {
    	char ch=getchar();
    	int f=1,x=0;
    	while(ch<'0'||ch>'9') {
    		if(ch=='-')
    			f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9') {
    		x=x*10+ch-'0';
    		ch=getchar();
    	}
    	return f*x;
    }
    
    const int maxn=2e5+10; 
    
    int n=0,ans=0;
    struct segment {
    	int l,r,col;
    	
    	const operator < (const segment& y) {
    		return r<y.r;
    	}
    }s[maxn];
    
    multiset<int> sel[2];
    
    signed main() {
    	n=read();
    	for(int i=1;i<=n;i++) {
    		s[i].l=read();
    		s[i].r=read();
    		s[i].col=read()-1;
    	}
    	
    	sort(s+1,s+n+1);
    	
    	ans=n;
    	for(int i=1;i<=n;i++) {
    		if(sel[1-s[i].col].lower_bound(s[i].l)!=sel[1-s[i].col].end()) {
    			ans--;
    			sel[1-s[i].col].erase(sel[1-s[i].col].lower_bound(s[i].l));
    		}
    		else {
    			sel[s[i].col].insert(s[i].r);
    		}
    	}
    	
    	cout<<ans<<endl;
    	return 0;
    }
    
    

    G. Directing Edges

    有点意思的一道题

    首先推荐阅读一下DP之神UM的一篇关于换根DP的blog。阅读完后再来看本题解效果更佳。

    好,我们切回正题。首先我们来简化条件。如果现在给我们的是一颗有 \(n\) 个点的树。让我们来做这个问题,我们该怎么做呢?比如我们考虑下图这棵树:

    G1

    我们容易想到我们要做的是 DP。我们定义:\(f_u\) 表示以 \(u\) 为根节点的子树里,保证 \(u\) 是饱和的情况下,我们所能获得的最大收益为多少。假设我们已经知道了所有 \(u\) 的子节点的值,即 \(f_{v_1},f_{v_2},f_{v_3}....f_{v_d}\) 的值(其中 \(v_1,v_2....v_d\)\(u\) 的子节点)那么我们现在需要考虑的就是连接 \(u,v_i\) 的边的方向到底是什么了。现在我们就来考虑一下 \(3\)\(f_1\) 的贡献。

    如果我们的特殊点是 \(3,9,11\),现在我们考虑蓝色的边的方向。我们发现所有的特殊点都在以 \(3\) 为根的子树中。为了让 \(1\) 饱和,那么蓝色的边的方向就应该是由 \(3\)\(1\),从而让所有的特殊点都指向 \(1\),同时 \(f_1\) 也得到了 \(f_3\) 的全部收益。这种情况只有在所有特殊点都在一颗子树中时成立,我们可以决定这课子树到其父节点这条边的方向。 如下图:

    G2

    如果我们的特殊点是 \(1,2,6,8\),现在我们考虑蓝色的边的方向。我们发现所有的特殊点都不在以 \(3\) 为根的子树中。为了获得 \(f_3\) 的收益,蓝色的边的方向就应该是由 \(1\)\(3\)这种情况只有在所有特殊点都在一颗子树外时成立,我们可以决定这课子树到其父节点这条边的方向。 如下图:

    G3

    还有一种情况就是在某一子树外和子树中都有特殊点,那么为了获得 \(f_3\) 的收益,同时让 \(1\) 饱和,我们必须要让这条边成为双向边。订单式如果这条边成为双向边的代价 \(w_i>f_3\)。我们宁愿抛弃 \(f_3\)。因为此时它带来的增益是负的。

    因此我们总结一下:对于一个节点 \(u\),已知他的儿子 \(f_{v_1},f_{v_2},f_{v_3}....f_{v_d}\) 的值。我们令连接 \(u,v_i\) 的边编号为 \(j\)\(size_u\) 为以 \(u\) 为根节点的子树中有特殊点个数,那么我们有:

    \[f_i=\sum\limits_{i=1}^d (f_{v_i}+calc(v_i)) \]

    \[clac(v)=\max \begin{cases}f_v&size_{v}=0,size_v=k\\f_v-e_j&f_v>e_j\\0&f_v\leq e_j\end{cases} \]

    显然,我们想要算 \(u\) 一定为饱和点的时候最大收益就是 \(u\) 为根的时候 \(f_u\) 的值。但是我们要把每个点的答案都算出来,时间复杂度就上升到了 \(O(n^2)\)。实在太大,无法接受。所以我们要采用换根DP的方式来优化复杂度。我们再来定义 \(dp_u\) 为满足 \(u\) 一定饱和的情况下,在以 \(u\) 的根节点的子树外的最大收益。我们先以 \(1\) 为根节点做一遍DFS求出 \(f\) 数组的值。接着再做一遍DFS,算出 \(dp\) 数组。具体的方法可以参照UM的blog,读者自己思考完成。实在不会可以看看代码。最后 \(ans_u=f_u+dp_u-c_u\)。因为 \(dp_u,f_u\) 中都包含 \(c_u\),也就是这个节点本身的收益。

    提示一下初值:\(dp_u=f_u=c_u\) 哦~

    但是我们上面讨论的是树的情况下。但如果是一张图呢?我们就要来进行一下问题的转化。

    我们一般把一个无向图转化为树的方法就是双联通分量缩点。我们来证明一下一个缩完点的双连通分量等同于树上的一个点。首先,我们知道一个双联通分量内部的边有向化,一定有至少一种方案使其成为强连通分量。所以这个双联通分量内部的点,要么全部饱和,要么全不饱和,其其内部的遍全有向化,代价为 \(0\)。因此,我们只需要缩个点,在进行换根DP即可解决此题。

    代码至少150+,大家自便(不压行)~

    //Don't act like a loser.
    //You can only use the code for studying or finding mistakes
    //Or,you'll be punished by Sakyamuni!!!
    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    
    int read() {
    	char ch=getchar();
    	int f=1,x=0;
    	while(ch<'0'||ch>'9') {
    		if(ch=='-')
    			f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9') {
    		x=x*10+ch-'0';
    		ch=getchar();
    	}
    	return f*x;
    }
    
    const int maxn=3e5+10;
    
    int n=0,m=0,k=0,h[maxn]={},cnt=0,low[maxn]={},dfn[maxn]={},col[maxn]={},num[maxn]={},size[maxn]={},d[maxn]={},tot=0;
    int col_cnt=0,time_cnt=0,w[maxn]={},c[maxn]={},val[maxn]={},spe[maxn]={},f[maxn]={},dp[maxn]={};
    vector<pair<int,int> > g[maxn];
    bool vis[maxn]={},spec[maxn];
    
    struct edge {
    	int v,w,next;
    }e[maxn<<1];
    
    void addedge(int u,int v,int w) {
    	e[++cnt].v=v;
    	e[cnt].w=w;
    	e[cnt].next=h[u];
    	h[u]=cnt; 
    }
    void insert(int u,int v,int w) {
    	addedge(u,v,w);
    	addedge(v,u,w);
    }
    
    void dfs1(int u,int fa) {
    	vis[u]=1;
    	low[u]=dfn[u]=++time_cnt;
    	int sz=g[u].size();
    	for(int i=0;i<sz;i++) {
    		int v=g[u][i].first;
    		
    		if(v!=fa) {
    			if(!vis[v]) {
    				dfs1(v,u);
    				low[u]=min(low[u],low[v]);
    			}
    			else {
    				low[u]=min(low[u],dfn[v]);
    			}
    		}
    	}
    	d[++tot]=u;
    }
    
    void dfs2(int u,int fa) {
    	col[u]=col_cnt;
    	spe[col_cnt]+=spec[u]? 1:0;
    	val[col_cnt]+=c[u];
    	
    	int sz=g[u].size();
    	for(int i=0;i<sz;i++) {
    		int v=g[u][i].first;
    		
    		if(dfn[u]>dfn[v]||low[v]>dfn[u]) {//判断是否是桥。
    			continue;
    		}
    		if(!col[v]) {
    			dfs2(v,u);
    		}
    	}
    }
    
    void tarjan() {
    	dfs1(1,0);
    	
    	for(int i=n;i;i--) {//反向循环
    		if(!col[d[i]]) {
    			col_cnt++;
    			dfs2(d[i],0);
    		}
    	}
    	
    	for(int u=1;u<=n;u++) {//缩点后的建图
    		int sz=g[u].size();
    		
    		for(int i=0;i<sz;i++) {
    			int v=g[u][i].first;
    			
    			if(u<v&&col[u]!=col[v]) {//注意一条边不要建两次~
    				insert(col[u],col[v],w[g[u][i].second]);
    			}
    		}
    	}
    }
    
    void dfs3(int u,int fa) {//以1为根的DFS
    	f[u]=val[u];
    	size[u]=spe[u];
    	for(int i=h[u];i;i=e[i].next) {
    		int v=e[i].v;
    		
    		if(v!=fa) {
    			dfs3(v,u);
    			size[u]+=size[v];
    			
    			if(size[v]==0||size[v]==k) {
    				f[u]+=f[v];
    			}
    			else {
    				f[u]+=max(0LL,f[v]-e[i].w);
    			}
    		}
    	}
    }
    
    void dfs4(int u,int fa) {//换根的部分
    	int sum=0;
    	
    	for(int i=h[u];i;i=e[i].next) {
    		int v=e[i].v;
    		
    		if(v!=fa) {
    			if(size[v]==k||size[v]==0) {//注意分类
    				sum+=f[v];
    			}
    			else {
    				sum+=max(0LL,f[v]-e[i].w);
    			}
    		}
    	}
    	
    	for(int i=h[u];i;i=e[i].next) {
    		int v=e[i].v;
    		
    		if(v!=fa) {
    			dp[v]=val[v];
    			if(size[v]==0||size[v]==k) {//分类,由上而下更新dp数组
    				dp[v]+=dp[u]+sum-f[v];
    			}
    			else {
    				dp[v]+=max(0LL,dp[u]+sum-max(0LL,f[v]-e[i].w)-e[i].w);
    			}
    			dfs4(v,u);
    		}
    	}
    }
    
    signed main() {
    	n=read();
    	m=read();
    	k=read();
    	
    	for(int i=1;i<=k;i++) {
    		spec[read()]=1;
    	}
    	for(int i=1;i<=n;i++) {
    		c[i]=read();
    	}
    	for(int i=1;i<=m;i++) {
    		w[i]=read();
    	}
    	for(int i=1;i<=m;i++) {
    		int a=read(),b=read();
    		g[a].push_back(make_pair(b,i));
    		g[b].push_back(make_pair(a,i));//第一次建图
    	} 
    	
    	tarjan();
    	
    	dfs3(1,0);
    	dp[1]=val[1];
    	dfs4(1,0);
    	
    	for(int i=1;i<=n;i++) {
    		printf("%lld ",dp[col[i]]+f[col[i]]-val[col[i]]);//注意-val[i]
    	}
    	
    	return 0;
    }
    
  • 相关阅读:
    【流量劫持】SSLStrip 终极版 —— location 瞒天过海
    【流量劫持】沉默中的狂怒 —— Cookie 大喷发
    【流量劫持】SSLStrip 的未来 —— HTTPS 前端劫持
    Web 前端攻防(2014版)
    流量劫持 —— 浮层登录框的隐患
    流量劫持能有多大危害?
    流量劫持是如何产生的?
    XSS 前端防火墙 —— 整装待发
    XSS 前端防火墙 —— 天衣无缝的防护
    XSS 前端防火墙 —— 无懈可击的钩子
  • 原文地址:https://www.cnblogs.com/huayucaiji/p/CF1389.html
Copyright © 2011-2022 走看看