11月刷题记录

①BZOJ1123 BLO(割点)

若选择的点是割点,则通过删除他的边能获得的每个子树的子树大小(siz)都与((n-siz))构成答案;所有割出的子树大小和为(sum),((n-sum-1))(sum+1)又能构成答案;最后还有该点和其他所有点构成答案((n-1))

若该点不是割点,删除并不会影响图的连通性,所以答案为(2*(n-1))

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)

const int maxn = 1e6 + 10;

struct edge {
	int to, next;
}e[maxn << 1];

int head[maxn], edge_cnt;

inline void add(int from,int to)
{
	e[++edge_cnt] = { to,head[from] };
	head[from] = edge_cnt;
}

int dfn[maxn], low[maxn], num;
ll ans[maxn], siz[maxn];
bool cut[maxn];

int n, m;

void tarjan(int from)
{
	dfn[from] = low[from] = ++num; siz[from] = 1;
	ans[from] = 0;
	int flag = 0;
	ll sum = 0;
	for (int i = head[from]; i != -1; i = e[i].next)
	{
		int to = e[i].to;
		if (!dfn[to])
		{
			tarjan(to);
			siz[from] += siz[to];
			low[from] = min(low[from], low[to]);
			if (low[to] >= dfn[from])
			{
				flag++;
				ans[from] += siz[to] * (n - siz[to]);
				sum += siz[to];
				if (from != 1 || flag > 1)
					cut[from] = 1;
			}
		}
		else
			low[from] = min(low[from], dfn[to]);
	}
	if (cut[from])
		ans[from] += (n - 1) + (n - sum - 1) * (sum + 1);
	else
		ans[from] = 2 * (n - 1);
}

int main() 
{
	fastio;
	cin >> n >> m;
	memset(head, -1, sizeof(head));
	while (m--)
	{
		int x, y;
		cin >> x >> y;
		if (x == y)continue;
		add(x, y), add(y, x);
	}
	num = 0;
	tarjan(1);
	for (int i = 1; i <= n; i++)
		cout << ans[i] << endl;
	return 0;
}

②Mr. Young's Picture Permutations(线性dp)

dp[a][b][c][d][e]表示从第一行到第五行分别的a、b、c、d、e人的合法状态集合的大小,每次转移可以这样理解:
将上一状态所有已排好人的身高全部++,然后在你需要转移的位置(选取的行的末尾)插入一个大小为1的人也合法。
(因为行人数从1到5逐行不升)
状态转移方程:太多了看代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)

const int maxn = 1e6 + 10;

ll dp[31][31][31][31][31];

int main()
{
	fastio;
	int k;
	while (cin >> k, k)
	{
		int s[6] = { 0 };
		for (int i = 1; i <= k; i++)cin >> s[i];
        memset(dp,0,sizeof(dp));
		dp[0][0][0][0][0] = 1;
		for (int a = 0; a <= s[1]; a++)
			for (int b = 0; b <= min(a, s[2]); b++)
				for (int c = 0; c <= min(b, s[3]); c++)
					for (int d = 0; d <= min(c, s[4]); d++)
						for (int e = 0; e <= min(d, s[5]); e++)
						{
							ll& x = dp[a][b][c][d][e];
							if (a && a - 1 >= b) x += dp[a - 1][b][c][d][e];
							if (b && b - 1 >= c) x += dp[a][b - 1][c][d][e];
							if (c && c - 1 >= d) x += dp[a][b][c - 1][d][e];
							if (d && d - 1 >= e) x += dp[a][b][c][d - 1][e];
							if (e) x += dp[a][b][c][d][e - 1];
						}
		cout << dp[s[1]][s[2]][s[3]][s[4]][s[5]] << endl;
	}
	return 0;
}

③LCIS(线性dp)

以dp[i][j]并不能表示以a[i]和b[j]结尾的两串的最长LCIS,因为这里为了避免重复转移(如果根据(LCS)转移方程当a[i]!=b[j]时(dp[i][j] = max(dp[i - 1][j],dp[i][j-1])),当(a[i]=2,b[j1]=b[j2]=2)时,会在a[i]==b[j]的转移中重复转移。因此转移只能dp[i][j]=dp[i-1][j]。之前得到的最长dp不一定能转移到dp[n][n].

因此dp[i][j]表示以a1ai和b1bj可以构成的(以bj为结尾的LCIS)的长度。

只有a[i]==b[j]时才需要从前面找一个合法状态+1取max,合法状态可以记录为val,每次新加入b[j]时当b[j]<a[i]更新val。

对样例dp数组打表:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)

const int maxn = 1e6 + 10;

int dp[3005][3005];

int main()
{
	fastio;
	int n;
	cin >> n;
	vector<int>a(n + 1), b(n + 1);
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	for (int i = 1; i <= n; i++)cin >> b[i];
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		int val = 0;
		for (int j = 1; j <= n; j++)
		{
			if (a[i] == b[j])dp[i][j] = val + 1;
			else dp[i][j] = dp[i - 1][j];//为什么不能写max(dp[i - 1][j], dp[i][j - 1]),因为当a[i]=2,b[j1]=b[j2]=2时,会重复转移
			if (b[j] < a[i])val = max(val, dp[i - 1][j]);
			ans = max(ans, dp[i][j]);
		}
	}
	cout << ans;
	return 0;

}

④LUOGU p1006传纸条(线性dp)

当路径不重合时:dp[a][b][c][d] = max(max(dp[a - 1][b][c - 1][d], dp[a - 1][b][c][d - 1]), max(dp[a][b - 1][c - 1][d], dp[a][b - 1][c][d - 1]))+ v[a][b] + v[c][d];

(a==c&&b==d)时只需要将v[a][b]加一次即可,这个重合状态会向下转移,不影响后续的不重合取数

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)

const int maxn = 1e6 + 10;

int dp[55][55][55][55];
int v[55][55];
int main()
{
	fastio;
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			cin >> v[i][j];
	for(int a=1;a<=n;a++)
		for(int b=1;b<=m;b++)
			for(int c=1;c<=n;c++)
				for (int d = 1; d <= m; d++)
				{
					dp[a][b][c][d] = max(max(dp[a - 1][b][c - 1][d], dp[a - 1][b][c][d - 1]), max(dp[a][b - 1][c - 1][d], dp[a][b - 1][c][d - 1]));
					dp[a][b][c][d] += v[a][b] + v[c][d];
					if (a == c && b == d)dp[a][b][c][d] -= v[a][b];
				}
	cout << dp[n][m - 1][n - 1][m];
	return 0;
}

⑤Mobile Service(线性DP)

如果设计状态dp[i][a][b][c],复杂度为(N*L^3),显然是会T的。

通过观察可以发现:每次只需移动1人,只需要在状态中记录不动的2个人的位置,另一个人的位置就是上一个请求的位置。
状态转移即:

	dp[i][a][b] = min(dp[i - 1][a][b] + c[last][p], dp[i][a][b]);
	dp[i][a][last] = min(dp[i][a][last], dp[i - 1][a][b] + c[b][p]);
	dp[i][last][b] = min(dp[i][last][b], dp[i - 1][a][b] + c[a][p]);

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);

const int maxn = 1e5 + 10;

int dp[1010][205][205];
int c[205][205];

int main()
{
	fastio;
	int L, n;
	cin >> L >> n;
	for (int i = 1; i <= L; i++)
		for (int j = 1; j <= L; j++)
			cin >> c[i][j];
	memset(dp, 0x3f, sizeof(dp));
	dp[0][1][2] = dp[0][2][1] = 0;
	int last = 3;
	for (int i = 1; i <= n; i++)
	{
		int p;
		cin >> p;
		for (int a = 1; a <= 200; a++)
		{
			for (int b = 1; b <= 200; b++)
			{
				if (a == b || a == last || b == last)continue;
				dp[i][a][b] = min(dp[i - 1][a][b] + c[last][p], dp[i][a][b]);
				dp[i][a][last] = min(dp[i][a][last], dp[i - 1][a][b] + c[b][p]);
				dp[i][last][b] = min(dp[i][last][b], dp[i - 1][a][b] + c[a][p]);
			}
		}
		last = p;
	}
	int ans = INT_MAX;
	for (int a = 1; a <= 200; a++)
		for (int b = 1; b <= 200; b++)
			ans = min(ans, min(dp[n][a][b], min(dp[n][a][last], dp[n][last][b])));
	cout << ans;
	return 0;

}

⑥AcWing395 冗余路径

将图进行v-dcc缩点,答案即缩点后(度为1的点的数量/2)上取整

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)
const int maxn = 1e6 + 10;

int head[maxn], edge_cnt;
struct edge {
    int to, next;
}e[maxn << 1];

inline void add(int from, int to)
{
    e[++edge_cnt] = { to,head[from] };
    head[from] = edge_cnt;
}

int dfn[maxn], low[maxn], num;
int c[maxn], dcc;
bool bridge[maxn << 1];
stack<int>s;

void tarjan(int from, int in_edge)
{
    dfn[from] = low[from] = ++num;
    s.push(from);
    for (int i = head[from]; ~i; i = e[i].next)
    {
        int to = e[i].to;
        if (!dfn[to])
        {
            tarjan(to, i);
            low[from] = min(low[from], low[to]);
            if (low[to] > dfn[from])
                bridge[i] = bridge[i ^ 1] = 1;
        }
        else if (i != (in_edge ^ 1))
            low[from] = min(low[from], dfn[to]);
        //用反向边更新追溯值
    }
    if (dfn[from] == low[from])
    {
        ++dcc;
        while (s.top() != from)
        {
            c[s.top()] = dcc;
            s.pop();
        }
        c[s.top()] = dcc;
        s.pop();
    }
}

vector<int>G[maxn];

int main()
{
    fastio;
    int n, m;
    cin >> n >> m;
    edge_cnt = 1;
    memset(head, -1, sizeof(head));
    while (m--)
    {
        int x, y;
        cin >> x >> y;
        add(x, y);
        add(y, x);
    }
    dcc = 0;
    tarjan(1, -1);

    for (int i = 2; i <= edge_cnt; i++)//遍历每一条边,正反都存
    {
        int x = e[i].to, y = e[i ^ 1].to;
        if (c[x] == c[y])continue;
        G[c[x]].push_back(c[y]);
    }

    int ans = 0;
    for (int i = 1; i <= dcc; i++)
    {
        //cout << i << " " << G[i].size() << endl;
        if (G[i].size()==1)
            ans++;
    }
    cout << (ans + 1) / 2;
    return 0;

}

⑦AcWing1183 电力

求出割点的同时记录能割出多少个dcc,答案=最开始的双连通分量数量+ (min(flag + (from != root)) - 1);

对于每个点from,flag为其dfn[from]>=low[to]的数量。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)
const int maxn = 1e6 + 10;

int head[maxn], edge_cnt;
struct edge {
    int to, next;
}e[maxn << 1];

inline void add(int from, int to)
{
    e[++edge_cnt] = { to,head[from] };
    head[from] = edge_cnt;
}

int dfn[maxn], low[maxn], root, num, ans, tot;

bool cut[maxn];

void tarjan(int from)
{
    //cout << from << endl;
    dfn[from] = low[from] = ++num;
    int flag = 0;
    for (int i = head[from]; ~i; i = e[i].next)
    {
        int to = e[i].to;
        if (!dfn[to])
        {
            tarjan(to);
            low[from] = min(low[from], low[to]);
            if (low[to] >= dfn[from])
            {
                flag++;
                if (from != root || flag > 1)
                    cut[from] = 1;
            }
        }
        else low[from] = min(low[from], dfn[to]);
        //用反向边更新追溯值
    }
    ans = max(ans, flag + (from != root));
}
int main()
{
    fastio;
    int n, m;
    while (cin >> n >> m, n || m)
    {
        edge_cnt = 1;
        memset(head, -1, sizeof(head));
        memset(dfn, 0, sizeof(dfn));
        ans = tot = num = 0;
        while (m--)
        {
            int x, y;
            cin >> x >> y;
            x++, y++;
            add(x, y);
            add(y, x);
        }
        for (int i = 1; i <= n; i++)
            if (!dfn[i])
            {
                tot++;
                root = i;
                tarjan(i);
            }
        cout<< tot + ans - 1 << endl;
    }

    return 0;

}

CF600E Lomsat gelral(dsu on tree模版)

题意:

有一棵 (n) 个结点的以 (1) 号结点为根的有根树。
每个结点都有一个颜色,颜色是以编号表示的, (i) 号结点的颜色编号为 (c_i)
如果一种颜色在以 (x) 为根的子树内出现次数最多,称其在以 (x) 为根的子树中占主导地位。显然,同一子树中可能有多种颜色占主导地位。
你的任务是对于每一个(i∈[1,n]),求出以 (i) 为根的子树中,占主导地位的颜色的编号和。
(n≤10^5,c_i≤n)

看上去暴力统计肯定会T。。。

用和轻重链剖分一样方法统计出重儿子,然后去暴力算答案,对于一个节点i:

暴力统计所有子树的贡献

若其为父节点的重儿子,则不需要清空他的贡献;

若其不是重儿子,则清空所有贡献

#include<bits/stdc++.h>
using namespace std;

#define ll long long
const int maxn = 1e5 + 10;
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)

struct edge{
    int to, next;
}e[maxn << 1];

int edge_cnt = 0, head[maxn];

inline void add(int from, int to)
{
    e[++edge_cnt] = { to,head[from] };
    head[from] = edge_cnt;
}

ll col[maxn], ans[maxn], cnt[maxn];

int siz[maxn], son[maxn];

void getson(int from, int fa)//统计重儿子
{
    int MAX = 0;
    siz[from] = 1;
    for (int i = head[from]; ~i; i = e[i].next)
    {
        int to = e[i].to;
        if (to == fa)continue;
        getson(to, from);
        siz[from] += siz[to];
        if (siz[to] > MAX)
            MAX = siz[to], son[from] = to;
    }
}

int Son;

ll sum = 0, MAX = 0;

void add(int from,int fa,int flag)//暴力统计答案
{
    ll &tot = cnt[col[from]];
    tot += flag;
    if (tot > MAX)MAX = tot, sum = col[from];
    else if (tot == MAX)sum += col[from];
    for (int i = head[from]; ~i; i = e[i].next)
    {
        int to = e[i].to;
        if (to == fa || to == Son)continue;
        add(to, from, flag);
    }
}

void dfs(int from, int fa, int opt)//dsu on tree
{
    for (int i = head[from]; ~i; i = e[i].next)
    {
        int to = e[i].to;
        if (to == fa)continue;
        if (to != son[from])dfs(to, from, 0);//先去计算轻儿子的答案
    }
    if (son[from])dfs(son[from], from, 1), Son = son[from];//再去计算重儿子的答案
    //计算完之后:

    add(from, fa, 1), Son = 0;//由于重儿子的答案不会被删除,只需要统计from轻儿子的答案
    ans[from] = sum;//计入答案
    if (!opt)add(from, fa, -1), sum = 0, MAX = 0;//如果这个点不是其父节点的重儿子,则要暴力清空答案
}

int main()
{
    fastio;
    int n;
    cin >> n;
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= n; i++)cin >> col[i];
    for (int i = 1; i < n; i++)
    {
        int x, y;
        cin >> x >> y;
        add(x, y);
        add(y, x);
    }
    getson(1, -1);
    dfs(1, 0, 0);
    for (int i = 1; i <= n; i++)
        cout << ans[i] << " ";
    return 0;

}

校oj Teacher Ma专场 H: 闪电五连鞭

最优方案肯定要每次取n-1个,可能有a[i]比较大,需要特判

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)
const int maxn = 5e4 + 10;
ll inf = 1e17 + 7;

int main()
{
	fastio;
	int n;
	cin >> n;
	vector<ll>a(n + 1);
	ll MAX = 0;
	ll sum = 0;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		sum += a[i];
		MAX = max(MAX, a[i]);
	}
	sum = (sum + n - 2) / (n - 1);
	cout << max(sum, MAX);

	return 0;

}

校oj Teacher Ma专场 I: 啪的一下,很快的哈

分层图最短路,就直接开个K维嗯转移就没了

这种dij大概不需要记录遍历过的点,毕竟每次贪心取得都是最小的距离,贪心之后不会再被其他的点更新

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)
const int maxn = 5e4 + 10;
ll inf = 1e17 + 7;

struct edge {
	ll to, cost, next;
}e[maxn<<1];

int edge_cnt = 0, head[maxn];

void add(int from, int to, ll cost)
{
	e[++edge_cnt] = { to,cost,head[from] };
	head[from] = edge_cnt;
}
int S, T, cnt = 0;

struct node {
	ll cost;
	int from;
	int cnt;
	friend bool operator <(node a, node b){
		return a.cost > b.cost;
	}
};

priority_queue<node>q;

ll dis[maxn][11];
int pre[maxn];
ll fee[maxn];
int n, m, K;

void dij(int n)
{
	while (!q.empty())q.pop();
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= K; j++)
			dis[i][j] = inf;
	dis[S][0] = 0;
	q.push({ 0,S,0 });
	while (!q.empty())
	{
		int from = q.top().from, cnt = q.top().cnt;
		q.pop();
		for (int i = head[from]; ~i; i = e[i].next)
		{
			int to = e[i].to, cost = e[i].cost;
			if (dis[to][cnt] > dis[from][cnt] + cost)
			{
				dis[to][cnt] = dis[from][cnt] + cost;
				q.push({ dis[to][cnt],to,cnt });
			}
			if (cnt < K && dis[to][cnt + 1]>dis[from][cnt])
			{
				dis[to][cnt + 1] = dis[from][cnt];
				q.push({ dis[to][cnt + 1] ,to,cnt + 1 });
			}
		}
	}
}

int main()
{
	fastio;
	memset(head, -1, sizeof(head));
	cin >> n >> m >> K;
	cin >> S >> T;
	while (m--)
	{
		ll x, y, cost;
		cin >> x >> y >> cost;
		add(x, y, cost), add(y, x, cost);
	}
	dij(n);
	ll ans = inf;
	for (int i = 0; i <= K; i++)
		ans = min(ans, dis[T][i]);
	cout << ans;
	return 0;

}
原文地址:https://www.cnblogs.com/ruanbaiQAQ/p/13944725.html