codeforces 825 E Minimal Labels

探讨了如何找到一个有向无环图(DAG)的拓扑排序序列,该序列在满足特定条件下字典序最小。通过反向思考并利用优先队列实现了一种有效的算法。

Problem

codeforces.com/contest/825/problem/E

Reference

看第 5 条评论

Meaning

给出一个n个结点的DAG,找一个给结点编号的序列,且满足3个条件:

  • 编号为 1~n,每个编号出现且仅出现一次;
  • 如果存在边从 f 指向 t,则结点 f 的编号要小于 t 的编号;
  • 在所有可行的编号序列中,取字典序最小的;

Analysis

第二个条件就像拓扑排序,再要满足足第三个条件,就在拓扑排序时,每次从所有入度为零的点中,找出最小的点并给它当前可用的最小的编号。
然而,这样做是错的。
比如样例:

---Input---
6 5
6 2
5 2
2 1
4 3
3 1
---Outpuut---
6 3 5 4 1 2

之前像上面那样写(有点像贪心),结果是:6 5 2 1 3 4
可能要先给某些大的点分配小的编号来“解锁”小的点,来取得更小的字典序。
按评论里给出的题解,是要反过来想,存反向边,每次也是考虑入度为零的点,但是选最大的点分配最大的编号。
评论里的证明原文(by krijgertje):

We can prove it by contradiction. Take any graph with the smallest number of nodes for which this algorithm does not give an optimal labeling. The largest label must always be assigned to a node with no outgoing edges, but it can’t be the largest of those nodes (otherwise the algorithm would give the optimal labeling). Lets call the largest node with no outgoing edges x and the node that has the largest label in the optimal labeling y. Then after labeling y with the largest label, the remaining part of the graph is correctly labeled by the algorithm (otherwise we would have a smaller graph for which the algorithm gives an incorrect result). This will first label some nodes greater than x and then it will label x. But we get a better labeling by labeling x with the largest label, y with the next largest and then label the same nodes as before. This is because of the nodes labeled so far y is the smallest node (since y < x and all other labelled nodes are greater than x) and in the partial labeling we have labeled the same nodes using the same labels. So we have a contradiction, which means our assumption was wrong and therefore there is no graph that our algorithm labels incorrectly.

Accepted Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int N = 100000, M = 100000;

vector<int> rev[N+1]; // reverse edge
int deg[N+1]; // degree of vertex
int label[N+1]; // label sequence
priority_queue<int> que;

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    memset(deg, 0, sizeof deg);
    for(int f, t, i=0; i<m; ++i)
    {
        scanf("%d%d", &f, &t);
        rev[t].push_back(f);
        ++deg[f];
    }
    for(int i=1; i<=n; ++i)
        if(!deg[i])
            que.push(i);
    for(int lab=n, t; lab > 0; --lab) // desc
    {
        t = que.top();
        que.pop();
        label[t] = lab;
        for(int i=0; i<rev[t].size(); ++i)
            if(--deg[rev[t][i]] == 0)
                que.push(rev[t][i]);
    }
    for(int i=1; i<=n; ++i)
        printf("%d%c", label[i], i==n?'\n':' ');
    return 0;
}

Wrong Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int N = 100000, M = 100000;

vector<int> g[N+1]; // edge
int deg[N+1];
int label[N+1];
priority_queue<int, vector<int>, greater<int> > que;

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    memset(deg, 0, sizeof deg);
    for(int f, t, i=0; i<m; ++i)
    {
        scanf("%d%d", &f, &t);
        g[f].push_back(t);
        ++deg[t];
    }
    for(int i=1; i<=n; ++i)
        if(!deg[i])
            que.push(i);
    for(int lab=1, t; lab <= n; ++lab) // ascend
    {
        t = que.top();
        que.pop();
        label[t] = lab;
        for(int i=0; i<g[t].size(); ++i)
            if(--deg[g[t][i]] == 0)
                que.push(g[t][i]);
    }
    for(int i=1; i<=n; ++i)
        printf("%d%c", label[i], i==n?'\n':' ');
    return 0;
}
### Codeforces 887E Problem Solution and Discussion The problem **887E - The Great Game** on Codeforces involves a strategic game between two players who take turns to perform operations under specific rules. To tackle this challenge effectively, understanding both dynamic programming (DP) techniques and bitwise manipulation is crucial. #### Dynamic Programming Approach One effective method to approach this problem utilizes DP with memoization. By defining `dp[i][j]` as the optimal result when starting from state `(i,j)` where `i` represents current position and `j` indicates some status flag related to previous moves: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = ...; // Define based on constraints int dp[MAXN][2]; // Function to calculate minimum steps using top-down DP int minSteps(int pos, bool prevMoveType) { if (pos >= N) return 0; if (dp[pos][prevMoveType] != -1) return dp[pos][prevMoveType]; int res = INT_MAX; // Try all possible next positions and update 'res' for (...) { /* Logic here */ } dp[pos][prevMoveType] = res; return res; } ``` This code snippet outlines how one might structure a solution involving recursive calls combined with caching results through an array named `dp`. #### Bitwise Operations Insight Another critical aspect lies within efficiently handling large integers via bitwise operators instead of arithmetic ones whenever applicable. This optimization can significantly reduce computation time especially given tight limits often found in competitive coding challenges like those hosted by platforms such as Codeforces[^1]. For detailed discussions about similar problems or more insights into solving strategies specifically tailored towards contest preparation, visiting forums dedicated to algorithmic contests would be beneficial. Websites associated directly with Codeforces offer rich resources including editorials written after each round which provide comprehensive explanations alongside alternative approaches taken by successful contestants during live events. --related questions-- 1. What are common pitfalls encountered while implementing dynamic programming solutions? 2. How does bit manipulation improve performance in algorithms dealing with integer values? 3. Can you recommend any online communities focused on discussing competitive programming tactics? 4. Are there particular patterns that frequently appear across different levels of difficulty within Codeforces contests?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值