题解 联合权值?改

传送门

考场上决策单调性+bitset水过的,但复杂度是假的,而且没开long long
正解没空写,先咕了

  • 无向图三元环:由度数大的点向度数小的点连边
    复杂度:令每条边对复杂度的贡献为 \(out_v\)(终点的出边个数),则
    \(out_v > \sqrt m\) 时,因为 \(u\) 的度数要大于 \(v\) 的度数,所以 \(u\) 的个数是 \(\frac{n}{\sqrt m}\) 级别的,这部分复杂度为 \(n\sqrt m\)
    \(out_v \leqslant \sqrt m\) 时,每条这样的边会带来 \(O(\sqrt m)\) 的复杂度,所以这部分是 \(O(m\sqrt m)\)
    所以整体就是 \(O(m\sqrt m)\)

    Code(无向图三元环计数):
    #include <bits/stdc++.h>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define N 100010
    #define ll long long
    #define pb push_back
    //#define int long long
    
    char buf[1<<21], *p1=buf, *p2=buf;
    #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
    inline int read() {
    	int ans=0, f=1; char c=getchar();
    	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
    	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
    	return ans*f;
    }
    
    int n, m;
    int head[N], size, cnt[N], ans, tim[N];
    vector<int> to[N];
    struct edge{int to, next;}e[N<<2];
    inline void add(int s, int t) {e[++size].to=t; e[size].next=head[s]; head[s]=size;}
    
    signed main()
    {
    	memset(head, -1, sizeof(head));
    	n=read(); m=read();
    	for (int i=1,u,v; i<=m; ++i) {
    		u=read(); v=read();
    		add(u, v); add(v, u);
    		++cnt[u]; ++cnt[v];
    	}
    	for (int i=1; i<=n; ++i) {
    		for (int j=head[i],v; ~j; j=e[j].next) {
    			v = e[j].to;
    			if (cnt[i]>cnt[v]) to[i].pb(v);
    			else if (cnt[i]==cnt[v]&&i<v) to[i].pb(v);
    		}
    	}
    	for (int i=1; i<=n; ++i) {
    		for (auto v:to[i]) tim[v]=i;
    		for (auto u:to[i]) {
    			for (auto v:to[u]) {
    				if (tim[v]==i) ++ans;
    			}
    		}
    	}
    	printf("%d\n", ans);
    
    	return 0;
    }
    
  • 在一个有 \(𝑚\) 条边的图中,三元环的个数为 \(𝑂(𝑚 \sqrt m)\) 的。显然一个点数为 \(O(\sqrt m)\) 的完全图可以使得三元环个数取到这个上界,或者考虑找三元环的过程也可以证明这个上界
    所以在有些题里,爆扫所有三元环的复杂度可能是对的

好了不做大鸽子了,更正解
看完题解发现我第一问复杂度是对的只是我不会证
证明:对于枚举的每条出边,一旦形成一次匹配就会break,而不形成匹配一定是出现了三元环,而三元环只有 \(m\sqrt m\)
所以复杂度是 \(O(m\sqrt m)\)
至于第二问:

  • 对于要求(图上)一些点权值的积/平方之类的,也许可以用到这样一个式子:

    \[(\sum a_i)^2 = \sum a_i^2 + \sum\limits_i\sum\limits_j a_ia_j[i\neq j] \]

    也即

    \[\sum\limits_i\sum\limits_j a_ia_j[i\neq j] = (\sum a_i)^2 - \sum a_i^2 \]

    常用于「求与一个点相连的所有点两两的权值的积的和」的 \(O(n^2)\)\(O(n)\) 过程

应用到这个题上,就是先枚举 \(w\),求出所有与 \(w\) 相连的点对的权值积的和
然后三元环也会被统计进来,所以枚举三元环并减去其贡献即可

image

Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 30010
#define ll long long
#define fir first
#define sec second
#define make make_pair
#define pb push_back
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m, t;
ll w[N];
vector<int> to[N];
pair<int, int> e[N];
bitset<30003> s[30003];

namespace force{
	ll maxn=-1, sum;
	void solve() {
		for (int i=1; i<=m; ++i) s[e[i].fir][e[i].sec]=1, s[e[i].sec][e[i].fir]=1;
		for (int i=1; i<=n; ++i) {
			for (int j=0; j<to[i].size(); ++j) {
				for (int k=0; k<j; ++k) {
					int u=to[i][j], v=to[i][k];
					if (!s[u][v]) {
						sum+=w[u]*w[v];
						maxn=max(maxn, w[u]*w[v]);
					}
				}
			}
		}
		printf("%d\n", t!=2?maxn:0);
		printf("%d\n", t!=1?sum*2:0);
		exit(0);
	}
}

namespace task1{
	ll maxn=-1;
	void solve() {
		for (int i=1; i<=m; ++i) s[e[i].fir][e[i].sec]=1, s[e[i].sec][e[i].fir]=1;
		for (int i=1; i<=n; ++i) {
			for (int j=to[i].size()-1; j>=0; --j) {
				for (int k=j-1; k>=0; --k) {
					int u=to[i][j], v=to[i][k], tem=w[u]*w[v];
					if (tem<=maxn) break;
					if (!s[u][v]) {
						maxn=max(maxn, tem);
					}
				}
			}
		}
		printf("%d\n", maxn);
		puts("0");
		exit(0);
	}
}

namespace task2{
	ll sum=0;
	void solve() {
		for (int i=1; i<=m; ++i) s[e[i].fir][e[i].sec]=1, s[e[i].sec][e[i].fir]=1;
		for (int i=1; i<=n; ++i) {
			int tem=0, t2;
			for (int j=to[i].size()-1; j>=0; --j) tem+=w[ to[i][j] ];
			for (int j=to[i].size()-1; j>=0; --j) {
				int u=to[i][j]; t2=tem-w[u];
				for (int k:to[u]) if (s[i][k]) t2-=w[k];
				sum+=w[u]*t2;
			}
		}
		puts("0");
		printf("%d\n", sum);
		exit(0);
	}
}

namespace task3{
	ll maxn=-1, sum=0;
	void solve() {
		for (int i=1; i<=m; ++i) s[e[i].fir][e[i].sec]=1, s[e[i].sec][e[i].fir]=1;
		for (int i=1; i<=n; ++i) {
			for (int j=to[i].size()-1; j>=0; --j) {
				for (int k=j-1; k>=0; --k) {
					int u=to[i][j], v=to[i][k], tem=w[u]*w[v];
					if (tem<=maxn) break;
					if (!s[u][v]) {
						maxn=max(maxn, tem);
					}
				}
			}
		}
		for (int i=1; i<=n; ++i) {
			int tem=0, t2;
			for (int j=to[i].size()-1; j>=0; --j) tem+=w[ to[i][j] ];
			for (int j=to[i].size()-1; j>=0; --j) {
				int u=to[i][j]; t2=tem-w[u];
				for (int k:to[u]) if (s[i][k]) t2-=w[k];
				sum+=w[u]*t2;
			}
		}
		printf("%d\n", t!=2?maxn:0);
		printf("%d\n", t!=1?sum:0);
		exit(0);
	}
}

signed main()
{
	freopen("link.in", "r", stdin);
	freopen("link.out", "w", stdout);

	// cout<<double(sizeof(s)+sizeof(to)+sizeof(e)+sizeof(w))/1024/1024<<endl;
	n=read(); m=read(); t=read();
	for (int i=1,u,v; i<=m; ++i) {
		u=read(); v=read();
		to[u].pb(v); to[v].pb(u);
		e[i]=make(u, v);
	}
	for (int i=1; i<=n; ++i) w[i]=read();
	for (int i=1; i<=n; ++i) sort(to[i].begin(), to[i].end(), [](int a, int b){return w[a]<w[b];});
	if (n<=100) force::solve();
	else if (t==1) task1::solve();
	else if (t==2) task2::solve();
	else task3::solve();

	return 0;
}
posted @ 2021-10-16 06:24  Administrator-09  阅读(1)  评论(0编辑  收藏  举报