zoukankan      html  css  js  c++  java
  • 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;
    
    }
    
  • 相关阅读:
    最小的K个数
    堆排序
    归并排序
    希尔排序
    快速排序
    二分查找
    数组中出现次数超过一半的数字
    包含min函数的栈
    栈的压入、弹出序列
    中缀表达式转后缀表达式
  • 原文地址:https://www.cnblogs.com/ruanbaiQAQ/p/13944725.html
Copyright © 2011-2022 走看看