YBTOJ 3.4强连通分量

A.有向图缩点

image
image

板子 讲解有时间补

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 200721;
int head[N], nxt[N], to[N], v[N], cnt;
int sccnum[N], sccsz[N], dfn[N], low[N], dfs_clock, scccnt;
int _head[N], _nxt[N], _to[N], _cnt;
int sccrd[N], q[N], tpx[N], dp[N], top, tp;
int n, m, ans;

void cmb(int x, int y) {
    to[++cnt] = y;
    nxt[cnt] = head[x];
    head[x] = cnt;
}

void _cmb(int x, int y) {
    _to[++_cnt] = y;
    _nxt[_cnt] = _head[x];
    _head[x] = _cnt;
}

void tarjan(int x) {
    dfn[x] = low[x] = ++dfs_clock;
    q[++top] = x;
    for (int i = head[x]; i; i = nxt[i]) {
        //		cout<<"1" ;
        int y = to[i];
        if (dfn[y] == 0)
            tarjan(y), low[x] = min(low[x], low[y]);
        else if (sccnum[y] == 0)
            low[x] = min(low[x], dfn[y]);
    }
    //	cout<<"1" ;
    if (low[x] == dfn[x]) {
        scccnt++;
        while (1) {
            int now = q[top];
            top--;
            sccnum[now] = scccnt;
            sccsz[scccnt] += v[now];
            //			cout<<"1" ;
            if (now == x)
                break;
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &v[i]);

    for (int i = 1; i <= m; ++i) {
        int x, y;
        scanf("%d%d", &x, &y);
        cmb(x, y);
    }

    for (int i = 1; i <= n; ++i) {
        if (dfn[i] == 0)
            tarjan(i);
    }
    //	cout<<"1" ;

    for (int i = 1; i <= n; ++i) {
        for (int j = head[i]; j; j = nxt[j]) {
            int y = to[j];
            if (sccnum[i] != sccnum[y]) {
                _cmb(sccnum[i], sccnum[y]);
                sccrd[sccnum[y]]++;
            }
        }
    }

    for (int i = 1; i <= scccnt; ++i) {
        if (sccrd[i] == 0)
            tpx[++tp] = i;
    }
    for (int i = 1; i <= tp; ++i) {
        for (int j = _head[tpx[i]]; j; j = _nxt[j]) {
            sccrd[_to[j]]--;
            if (sccrd[_to[j]] == 0)
                tpx[++tp] = _to[j];
        }
    }

    for (int i = 1; i <= scccnt; ++i) {
        dp[i] = sccsz[i];
    }

    for (int i = 1; i <= tp; ++i) {
        for (int j = _head[tpx[i]]; j; j = _nxt[j]) dp[_to[j]] = max(dp[_to[j]], sccsz[_to[j]] + dp[tpx[i]]);
    }

    for (int i = 1; i <= scccnt; ++i) ans = max(ans, dp[i]);

    printf("%d\n", ans);
    //	for( int i = 1 ; i <= n ; ++i )
    //	cout<<i<<" "<<sccnum[i]<<" "<<sccsz[sccnum[i]]<<endl ;

    return 0;
}

upd:实际上 \(Tarjan\) 缩完点之后的编号顺序就是逆拓扑序 不需要排拓扑(


B.受欢迎的牛

image
image

实际上受欢迎的牛就是从任何点出发 都能到达的点
想到 \(DAG\) 上的 \(DP\)
但是题目不保证图是个 \(DAG\)
这时发现一个强连通图里 所有的牛都互相喜欢 所以缩点对于信息没有影响
然后就可以跑缩点
然后就发现当缩完只有一个点的出度为 \(0\) 那么结尾那个强连通分量的所有点都是受欢迎的牛
否则就没有受欢迎的牛

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 50721;
int head[N], to[N], nxt[N], vis[N], sccnum[N], sccsz[N], rd[N], cd[N], dfn[N], low[N], s[N];
int n, m, cnt, scccnt, dfs_clock, top;

void cmb(int x, int y) {
    to[++cnt] = y;
    nxt[cnt] = head[x];
    head[x] = cnt;
}

void dfs(int u) {
    vis[u] = 1;
    dfn[u] = low[u] = ++dfs_clock;
    s[++top] = u;
    for (int i = head[u]; i; i = nxt[i]) {
        if (!dfn[to[i]]) {
            dfs(to[i]);
            low[u] = min(low[u], low[to[i]]);
        } else if (!sccnum[to[i]])
            low[u] = min(low[u], dfn[to[i]]);
    }
    if (low[u] == dfn[u]) {
        scccnt++;
        while (1) {
            int x = s[top];
            top--;
            sccnum[x] = scccnt;
            sccsz[scccnt]++;
            if (x == u)
                break;
        }
    }
}

void sd(int x) {
    for (int i = head[x]; i; i = nxt[i]) {
        if (sccnum[x] != sccnum[to[i]]) {
            cd[sccnum[x]]++;
            rd[sccnum[to[i]]]++;
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        cmb(u, v);
    }

    for (int i = 1; i <= n; i++) {
        if (!vis[i])
            dfs(i);
    }

    for (int i = 1; i <= n; i++) {
        sd(i);
    }

    int sum = 0, mem;
    for (int i = 1; i <= scccnt; i++) {
        if (cd[i] == 0) {
            sum++;
            mem = i;
        }
        //		cout<<i<<" "<<cd[i]<<endl ;
    }

    if (sum == 1) {
        printf("%d", sccsz[mem]);
    } else if (sum == 0)
        printf("%d", n);
    else
        printf("0");

    return 0;
}

C.最大半连通子图

image
image

信息题 一般看到这种题都很谔谔
但是还是要硬着头皮分析一下什么意思
主要就是说 在一个半连通图中 不存在一对都无法到达的点
那长啥样的图不是一个半连通图呢
比如这样
image
进而思考 如果这图是个 \(DAG\) 那么半连通图一定是一条链
那么我们可不可以考虑缩点呢
发现一个强连通图一定是一个半连通图 并且里面的点可以互相到达
所以缩点对于信息没有影响
然后就转化成了 \(DAG\) 上的一个 \(DP\)
转移式非常显然 看代码就能看懂

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 0721;
int head[N], nxt[N], to[N], cnt;
int sccnum[N], sccsz[N], dfn[N], low[N], q[N], dfs_clock, scccnt, top;
int head1[N], nxt1[N], to1[N], cnt1;
int last[N], ans[N], f[N];
int n, m, mod;

inline void cmb(int x, int y) {
	to[++cnt] = y;
	nxt[cnt] = head[x];
	head[x] = cnt;
}

inline void tarjan(int x) {
	dfn[x] = low[x] = ++dfs_clock;
	q[++top] = x;
	for (int i = head[x]; i; i = nxt[i]) {
		int y = to[i];
		if (!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x], low[y]);
		}
		else if (!sccnum[y])
			low[x] = min(low[x], dfn[y]);
	}
	if (low[x] == dfn[x]) {
		++scccnt;
		while(1) {
			int now = q[top];
			--top;
			sccnum[now] = scccnt;
			++sccsz[scccnt];
			if (x == now)
				break;
		}
	}
}

inline void cmb1(int x, int y) {
	to1[++cnt1] = y;
	nxt1[cnt1] = head1[x];
	head1[x] = cnt1;
}

int main() {
	scanf("%d%d%d", &n, &m, &mod);
	for (int i = 1; i <= m; ++i) {
		int x, y;
		scanf("%d%d", &x, &y);
		cmb(x, y);
	}
	
	for (int i = 1; i <= n; ++i) {
		if (!dfn[i])
			tarjan(i);
	}
	
	for (int i = 1; i <= n; ++i) {
		for (int j = head[i]; j; j = nxt[j]) {
			int y = to[j];
			if (sccnum[y] != sccnum[i])
				cmb1(sccnum[i], sccnum[y]);
		}
	}
	
	for (int i = 1; i <= scccnt; ++i) {
		f[i] = sccsz[i];
		ans[i] = 1;
	}
	
	for (int i = scccnt; i >= 1; --i) {
		for (int j = head1[i]; j; j = nxt1[j]) {
			int y = to1[j];
			if (i == last[y])
				continue;
//			cout << i << " " << y << endl;
			last[y] = i;
			if (f[y] < f[i] + sccsz[y]) {
				f[y] = f[i] + sccsz[y];
				ans[y] = ans[i];
			}
			else if (f[y] == f[i] + sccsz[y]) {
				ans[y] += ans[i];
				ans[y] %= mod;
			}
//			cout << i << " " << y << endl;
//			cout << f[y] << " " << ans[y] << endl;
		}
	}
	
	int sum = 0, tmp = 0;
	for (int i = 1; i <= scccnt; ++i) {
		if (sum < f[i]) {
			sum = f[i];
			tmp = ans[i];
		}
		else if(sum == f[i]) {
			tmp += ans[i];
			tmp %= mod;
		}
	}
	
	printf("%d\n%d",sum,tmp);

	return 0;
}

D.恒星的亮度

image
image

不了解差分约束的请戳差分约束学习笔记
但是这题不能跑最短路 至少理论不能
我们还是思考 这题和普通的差分约束题有什么区别
发现这题的边权只有 \(1\) \(-1\)\(0\) 之分
并且我们可以通过某种方式让边权只有 \(0\)\(1\)
那么假如有一条由 \(x\)\(y\) 的边 就一定有 \(dis[y] \ge dis[x]\)
那么如果有解的话 当 \(x\)\(y\) 强连通时 一定有 \(dis[x] = dis[y]\)
然后就是 \(Tarjan\) + \(DP\)

点击查看代码
#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int N = 3e5 + 0721;
int head[N], to[N], nxt[N], len[N], cnt;
int sccnum[N], sccsz[N], sccsiz[N], dfn[N], low[N], q[N], dfs_clock, scccnt, top;
int head1[N], to1[N], nxt1[N], len1[N], cnt1;
int dis[N];
int n, m;

inline void cmb(int x, int y, int z) {
	to[++cnt] = y;
	len[cnt] = z;
	nxt[cnt] = head[x];
	head[x] = cnt;
}

inline void tarjan(int x) {
	dfn[x] = low[x] = ++dfs_clock;
	q[++top] = x;
	for (int i = head[x]; i; i = nxt[i]) {
		int y = to[i];
		if (!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x], low[y]);
		} else if (!sccnum[y]) {
			low[x] = min(low[x], dfn[y]);
		}
	}
	if (dfn[x] == low[x]) {
		++scccnt;
		while(1) {
			int now = q[top];
			--top;
			sccnum[now] = scccnt;
			++sccsiz[scccnt];
			if (now == x)
				break;
		}
	}
}

inline void cmb1(int x, int y, int z) {
	to1[++cnt1] = y;
	len1[cnt1] = z;
	nxt1[cnt1] = head1[x];
	head1[x] = cnt1;
}

int main() {
	
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int x, y, num;
		scanf("%d%d%d", &num, &x, &y);
		if (num == 1)
			cmb(x, y, 0), cmb(y, x, 0);
		else if (num == 2)
			cmb(x, y, 1);
		else if (num == 3)
			cmb(y, x, 0);
		else if (num == 4)
			cmb(y, x, 1);
		else
			cmb(x, y, 0);
	}
	
	for(int i = 1; i <= n; ++i) cmb(0, i, 0);

	tarjan(0);
	
	for (int i = 0; i <= n; ++i) {
		for (int j = head[i]; j; j = nxt[j]) {
			int y = to[j];
			if (sccnum[i] != sccnum[y])
				cmb1(sccnum[i], sccnum[y], len[j]);
			else
				sccsz[sccnum[i]] += len[j];
		}
	}
	
	for (int i = 1; i <= scccnt; ++i) {
		if (sccsz[i] != 0) {
			printf("-1");
			return 0;
		}
	}
//	cout<<"!";
	ll ans = 0;
	
	for (int i = 1; i <= scccnt; ++i)
	dis[i] = 1;
	
	for (int i = scccnt; i >= 1; --i) {
		for (int j = head1[i]; j; j = nxt1[j]) {
			int y = to1[j];
//			cout<<y<<" "<<dis[y]<<" "<<len1[j]<<endl;
			dis[y] = max(dis[y], dis[i] + len1[j]);
		}
	}
	
	for (int i = 1; i < scccnt; ++i)
	ans += (ll)dis[i] * sccsiz[i];
	
	printf("%lld",ans);
	
	return 0;
}

E.网络传输

image
image

没啥好说的 缩点 DP 即可

建议封装链前 别像下面那个代码写两套系统结果忘了一个 \(\texttt_\) 修了好久

点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

namespace steven24 {

const int N = 2e5 + 0721;
const int M = 1e6 + 0721;

int head[N], nxt[M], to[M], len[M], cnt;
int _head[N], _nxt[M], _to[M], _len[M], _cnt;
int sccnum[N], scccnt;
int dfn[N], low[N], dfs_clock;
int st[N], top;
ll f[N];
int n, m;

inline int read() {
	int xr = 0, F = 1;
	char cr;
	while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
	while (cr >= '0' && cr <= '9')
		xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
	return xr * F;
}

inline void add_edge(int x, int y, int z) {
	to[++cnt] = y;
	nxt[cnt] = head[x];
	head[x] = cnt;
	len[cnt] = z;
}

inline void _add_edge(int x, int y, int z) {
//	cout << x << " " << y << " " << z << "\n";
	_to[++_cnt] = y;
	_nxt[_cnt] = _head[x];
	_head[x] = _cnt;
	_len[_cnt] = z;
}

void tarjan(int x) { 
	dfn[x] = low[x] = ++dfs_clock;
	st[++top] = x;
	for (int i = head[x]; i; i = nxt[i]) {
		int y = to[i];
		if (!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x], low[y]);
		} else if (!sccnum[y])
			low[x] = min(low[x], dfn[y]);
	} 
	
	if (dfn[x] == low[x]) {
		++scccnt;
		while (top) {
			int now = st[top];
//			cout << x << " " << now << "\n";
			--top;
			sccnum[now] = scccnt;
			if (now == x) break;
		}
	}
}

void main() {
	n = read(), m = read();
	for (int i = 1; i <= m; ++i) {
		int x, y, z;
		x = read(), y = read(), z = read();
		add_edge(x, y, z);
	}
	for (int i = 1; i <= n; ++i) {
		if (!dfn[i]) tarjan(i);
	}
	
	for (int i = 1; i <= n; ++i) {
		for (int j = head[i]; j; j = nxt[j]) {
			int y = to[j];
			if (sccnum[i] == sccnum[y]) continue;
			_add_edge(sccnum[i], sccnum[y], len[j]);
		}
	}
	
//	for (int i = 1; i <= n; ++i) cout << i << " " << sccnum[i] << "\n";
	
	memset(f, 0x3f, sizeof f);
	f[sccnum[1]] = 0;
	for (int i = scccnt; i >= 1; --i) {
		for (int j = _head[i]; j; j = _nxt[j]) {
			int y = _to[j];
//			cout << y << " " << f[i] << " " << f[y] << " " << _len[j] << "\n";
			f[y] = min(f[y], f[i] + _len[j]);
		}
	}
	
	printf("%lld\n", f[sccnum[n]]);
}

}

int main() {
	steven24::main();
	return 0;
}
/*
5 5
1 2 1
2 3 6
3 4 1
4 2 1
3 5 2
*/

F.通讯问题

image
image
image

这题没做出来 啥啊。。

考虑记录前置状态 假了
考虑 bfs 假了
考虑最小费用最大流 不知道假没假但是狗都不写

题解:贪心的选能到达它的最小的边
我:6

结果多测写了 init 函数主函数没调用 蠢到家了。

点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

namespace steven24 {

const int N = 2e5 + 0721;
const int M = 1e6 + 0721;

int head[N], nxt[M], to[M], len[M], cnt;
int _head[N], _nxt[M], _to[M], _len[M], _cnt;
int sccnum[N], scccnt;
int dfn[N], low[N], dfs_clock;
int st[N], top;
ll f[N];
int n, m;

inline int read() {
	int xr = 0, F = 1;
	char cr;
	while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
	while (cr >= '0' && cr <= '9')
		xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
	return xr * F;
}

void init() {
	memset(head, 0, sizeof head);
	memset(_head, 0, sizeof _head);
	memset(dfn, 0, sizeof dfn);
	memset(low, 0, sizeof low);
	memset(sccnum, 0, sizeof sccnum);
	cnt = _cnt = top = 0;
	scccnt = dfs_clock = 0;
}

inline void add_edge(int x, int y, int z) {
	to[++cnt] = y;
	nxt[cnt] = head[x];
	head[x] = cnt;
	len[cnt] = z;
}

inline void _add_edge(int x, int y, int z) {
	_to[++_cnt] = y;
	_nxt[_cnt] = _head[x];
	_head[x] = _cnt;
	_len[_cnt] = z;
}

void tarjan(int x) { 
	dfn[x] = low[x] = ++dfs_clock;
	st[++top] = x;
	for (int i = head[x]; i; i = nxt[i]) {
		int y = to[i];
		if (!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x], low[y]);
		} else if (!sccnum[y])
			low[x] = min(low[x], dfn[y]);
	} 
	
	if (dfn[x] == low[x]) {
		++scccnt;
		while (top) {
			int now = st[top];
			--top;
			sccnum[now] = scccnt;
			if (now == x) break;
		}
	}
}

void main() {
	while (1) {
		n = read(), m = read();
		init();
		if (n == 0 && m == 0) break;
		for (int i = 1; i <= m; ++i) {
			int x, y, z;
			x = read(), y = read(), z = read();
			++x, ++y;
			add_edge(x, y, z);
		}
		for (int i = 1; i <= n; ++i) {
			if (!dfn[i]) tarjan(i);
		}
		
		for (int i = 1; i <= n; ++i) {
			for (int j = head[i]; j; j = nxt[j]) {
				int y = to[j];
				if (sccnum[i] == sccnum[y]) continue;
				_add_edge(sccnum[i], sccnum[y], len[j]);
			}
		}
	
//		for (int i = 1; i <= n; ++i) cout << i << " " << sccnum[i] << "\n"; 
		memset(f, 0x3f, sizeof f);
		f[sccnum[1]] = 0;
		
		for (int i = scccnt; i >= 1; --i) {
			for (int j = _head[i]; j; j = _nxt[j]) {
				int y = _to[j];
				f[y] = min(f[y], (ll)_len[j]);
			}
		}
		
//		for (int i = 1; i <= n; ++i) cout << i << " " << pre[i] << " " << f[i] << "\n";
		
		ll ans = 0;
		for (int i = 1; i <= scccnt; ++i) ans += f[i];
		printf("%lld\n", ans);
	}
}

}

int main() {
	steven24::main();
	return 0;
}
/*
3 3
0 1 100
1 2 50
0 2 100
3 3
0 1 100
1 2 50
2 1 100
2 2
0 1 50
0 1 100
0 0
*/

G.删点次数

image
image

首先看到部分分 考虑 DAG 的情况

发现实际上就是最长链的长度

那么对于一个强连通分量 我们盲猜需要删的次数就为它的大小

一点碎碎念:

3 4
1 2
2 1
2 3
3 2

答案应为 2 但是 AC 程序输出 3
猜想 std 应该也是有问题的 总是要么是这做法假了要么真是这题有问题(

但是如果指的是每次删的点在原图中不能互相到达似乎就说的通了(?
那看来还是我理解问题

点击查看代码
#include <bits/stdc++.h>
using namespace std;

namespace steven24 {
	
const int N = 1e6 + 0721;
int dfn[N], low[N], dfs_clock;
int sccnum[N], sccsz[N], scccnt;
int st[N], top;
int f[N];
int n, m;

struct graph {
	int head[N], nxt[N], to[N], cnt;
	inline void add_edge(int x, int y) {
		to[++cnt] = y;
		nxt[cnt] = head[x];
		head[x] = cnt;
	}
};
graph G1, G2;

inline int read() {
	int xr = 0, F = 1;
	char cr;
	while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
	while (cr >= '0' && cr <= '9')
		xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
	return xr * F;
}

void tarjan(int x) {
	dfn[x] = low[x] = ++dfs_clock;
	st[++top] = x;
	for (int i = G1.head[x]; i; i = G1.nxt[i]) {
		int y = G1.to[i];
		if (!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x], low[y]);
		} else if (!sccnum[y])
			low[x] = min(low[x], dfn[y]);
	}
	
	if (dfn[x] == low[x]) {
		++scccnt;
		while (1) {
			int now = st[top];
			--top;
			sccnum[now] = scccnt;
			++sccsz[scccnt];
			if (now == x) break;
		}
	}
}

void main() {
	n = read(), m = read();
	for (int i = 1; i <= m; ++i) {
		int x, y;
		x = read(), y = read();
		G1.add_edge(x, y);
	}
	for (int i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i);
	
	for (int i = 1; i <= n; ++i) {
		for (int j = G1.head[i]; j; j = G1.nxt[j]) {
			int y = G1.to[j];
			if (sccnum[y] == sccnum[i]) continue;
			G2.add_edge(sccnum[i], sccnum[y]);
		}
	}
	
	for (int i = 1; i <= scccnt; ++i) f[i] = sccsz[i];
	for (int i = scccnt; i >= 1; --i) {
		for (int j = G2.head[i]; j; j = G2.nxt[j]) {
			int y = G2.to[j];
			f[y] = max(f[y], f[i] + sccsz[y]);
		}
	}
	
	int ans = 0;
	for (int i = 1; i <= scccnt; ++i) ans = max(ans, f[i]);
	printf("%d\n", ans);
}
	
}

int main() {
	steven24::main();
	return 0;
}
/*
5 4 
1 2 
2 3
3 1 
4 5
*/

H.软件安装

image
image

树上背包

考虑有可能互相依赖 所以要缩点

  • 注意全缩完之后判入度再连 0

  • 注意树上背包外层循环倒序枚举

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;

namespace steven24 {
	
const int N = 521;
int dfn[N], low[N], dfs_clock;
int sccnum[N], sccw[N], sccv[N], scccnt;
int st[N], top;
ll f[N][N]; // 第i个点 重量为j时的最大价值
int w[N], v[N], d[N];
int rd[N];
int n, m;

struct graph {
	int head[N], nxt[N], to[N], cnt;
	inline void add_edge(int x, int y) {
		to[++cnt] = y;
		nxt[cnt] = head[x];
		head[x] = cnt;
	}
};
graph G1, G2;

inline int read() {
	int xr = 0, F = 1;
	char cr;
	while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
	while (cr >= '0' && cr <= '9')
		xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
	return xr * F;
}

void tarjan(int x) {
	dfn[x] = low[x] = ++dfs_clock;
	st[++top] = x;
	for (int i = G1.head[x]; i; i = G1.nxt[i]) {
		int y = G1.to[i];
		if (!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x], low[y]);
		} else if (!sccnum[y])
			low[x] = min(low[x], dfn[y]);
	}
	
	if (dfn[x] == low[x]) {
		++scccnt;
		while (1) {
			int now = st[top];
			--top;
			sccnum[now] = scccnt;
			sccw[scccnt] += w[now];
			sccv[scccnt] += v[now];
			if (now == x) break;
		}
	}
}

void dfs(int x) {
	for (int i = sccw[x]; i <= m; ++i) f[x][i] = sccv[x];
	for (int i = G2.head[x]; i; i = G2.nxt[i]) {
		int y = G2.to[i];
		dfs(y);
		for (int j = m - sccw[x]; j >= 0; --j) {
			for (int k = 0; k <= j; ++k) {
				f[x][j + sccw[x]] = max(f[x][j + sccw[x]], f[y][k] + f[x][j + sccw[x] - k]);
			}
		}
	}
}

void main() {
	n = read(), m = read();
	for (int i = 1; i <= n; ++i) w[i] = read();
	for (int i = 1; i <= n; ++i) v[i] = read();
	for (int i = 1; i <= n; ++i) d[i] = read();
	for (int i = 1; i <= n; ++i) if (d[i]) G1.add_edge(d[i], i);
	for (int i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i);
	
	for (int i = 1; i <= n; ++i) {
		for (int j = G1.head[i]; j; j = G1.nxt[j]) {
			int y = G1.to[j];
			if (sccnum[y] == sccnum[i]) continue;
			G2.add_edge(sccnum[i], sccnum[y]);
			++rd[sccnum[y]];
		}
	}
	for (int i = 1; i <= scccnt; ++i) if (!rd[i]) G2.add_edge(0, i);
	dfs(0);
	
	printf("%lld\n", f[0][m]);
}
	
}

signed main() {
	steven24::main();
	return 0;
}
/*
3 10
5 5 6
2 3 4
0 1 1
*/

I.宫室宝藏

image
image

这题在 lg 竟然是个紫题。。。

主要问题在于建图

考虑同一行有横行传送门的点 我们显然不能把每两个点间都连一条边 这样是 \(n ^ 2\)
但是我们发现如果 \(1, 2\) 联通并且 \(2, 3\) 那么一定有 \(1,3\) 联通
所以我们把相邻的两条边联通就可以了

那么对于在这一行但是没有横行传送门的点 选取任意一个在这一行并且有横行传送门的点 连一条单向边即可

竖列同理

对于九宫格 因为最多只有 \(8\) 条单向边 暴力查即可
注意 \(R \times C \le 10^{12}\) 先压成一维再查

连完边就直接缩点求最长链即可 可以连超级源点和超级汇点方便统计答案

点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

namespace steven24 {
	
const int N = 1e6 + 0721;
int dfn[N], low[N], dfs_clock;
int sccnum[N], sccsz[N], scccnt;
int st[N], top;
int f[N];
int dx[8] = {1, 1, 1, 0, 0, -1, -1, -1};
int dy[8] = {1, 0, -1, 1, -1, -1, 1, 0};
vector<int> h[N], l[N];
map<ll, int> vis;
int n, R, C;

struct graph {
	int head[N], nxt[N], to[N], cnt;
	inline void add_edge(int x, int y) {
		to[++cnt] = y;
		nxt[cnt] = head[x];
		head[x] = cnt;
	}
};
graph G1, G2;

struct treasure {
	int x, y, id, opt;
} a[N];

inline int read() {
	int xr = 0, F = 1;
	char cr;
	while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
	while (cr >= '0' && cr <= '9')
		xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
	return xr * F;
}

inline ll change(int x, int y) {
	return 1ll * (x - 1) * C + y;
}

void connect (int x, int y, int id) {
	for (int i = 0; i < 8; ++i) {
		int tx = x + dx[i], ty = y + dy[i];
		if (tx < 1 || tx > R || ty < 1 || ty > C) continue;
		if (vis.find(change(tx, ty)) != vis.end()) G1.add_edge(id, vis[change(tx, ty)]);
	}
}

void tarjan(int x) {
	dfn[x] = low[x] = ++dfs_clock;
	st[++top] = x;
	for (int i = G1.head[x]; i; i = G1.nxt[i]) {
		int y = G1.to[i];
		if (!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x], low[y]);
		} else if (!sccnum[y])
			low[x] = min(low[x], dfn[y]);
	}
	
	if (dfn[x] == low[x]) {
		++scccnt;
		while (1) {
			int now = st[top];
			--top;
			sccnum[now] = scccnt;
			++sccsz[scccnt];
			if (now == x) break;
		}
	}
}

void main() {
	n = read(), R = read(), C = read();
	for (int i = 1; i <= n; ++i) {
		a[i].x = read(), a[i].y = read(), a[i].opt = read();
		a[i].id = i;
		switch (a[i].opt) {
			case 1 : {
				h[a[i].x].push_back(i);
				break;
			}
			case 2 : {
				l[a[i].y].push_back(i);
				break;
			}
			default : {
				break;
			}
		}
		vis[change(a[i].x, a[i].y)] = i;
	}
	
	for (int i = 1; i <= R; ++i) {
		if (!h[i].size()) continue;
		for (int j = 0; j < (int)h[i].size() - 1; ++j) {
			G1.add_edge(h[i][j], h[i][j + 1]);
			G1.add_edge(h[i][j + 1], h[i][j]);
		}
	}
	for (int i = 1; i <= C; ++i) {
		if (!l[i].size()) continue;
		for (int j = 0; j < (int)l[i].size() - 1; ++j ) {
			G1.add_edge(l[i][j], l[i][j + 1]);
			G1.add_edge(l[i][j + 1], l[i][j]);
		}
	}
	
	for (int i = 1; i <= n; ++i) {
		int x = a[i].x, y = a[i].y, opt = a[i].opt;
		switch (opt) {
			case 1 : {
				if (l[y].size()) G1.add_edge(l[y][0], i);
				break;
			}
			case 2 : {
				if (h[x].size()) G1.add_edge(h[x][0], i);
				break;
			}
			default : {
				if (l[y].size()) G1.add_edge(l[y][0], i);
				if (h[x].size()) G1.add_edge(h[x][0], i);
				break;
			}
		}
	}
	
	for (int i = 1 ;i <= n; ++i) {
		if (a[i].opt != 3) continue;
		connect(a[i].x, a[i].y, i); 
	}
	
	for (int i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i);
	
	for (int i = 1; i <= n; ++i) {
		for (int j = G1.head[i]; j; j = G1.nxt[j]) {
			int y = G1.to[j];
			if (sccnum[y] == sccnum[i]) continue;
			G2.add_edge(sccnum[i], sccnum[y]);
		}
	}
	
	for (int i = 1; i <= scccnt; ++i) f[i] = sccsz[i];
	for (int i = 1; i <= n; ++i) {
		G2.add_edge(scccnt + 1, i);
		G2.add_edge(i, 0);
	}
	for (int i = scccnt + 1; i >= 0; --i) {
		for (int j = G2.head[i]; j; j = G2.nxt[j]) {
			int y = G2.to[j];
			f[y] = max(f[y], f[i] + sccsz[y]);
		}
	}
	
	printf("%d\n", f[0]);
}
	
}

int main() {
	steven24::main();
	return 0;
}
/*
10 7 7
2 2 1
2 4 2
1 7 2
2 7 3
4 2 2
4 4 1
6 7 3
7 7 1
7 5 2
5 2 1
*/
posted @ 2023-06-29 14:37  Steven24  阅读(49)  评论(0编辑  收藏  举报