• Codeforces Round #594 (Div. 2)


    A - Integer Points

    题意:给n条斜率为1和m条斜率为-1的直线,求交点在整点的个数。

    题解:观察下面给的图,发现截距相差为偶数的点才有交点在整点。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
     
    int main() {
    #ifdef KisekiPurin
        freopen("KisekiPurin.in", "r", stdin);
    #endif // KisekiPurin
        int t;
        scanf("%d", &t);
        while(t--) {
            int n;
            scanf("%d", &n);
            int p[2] = {}, q[2] = {};
            while(n--) {
                int pi;
                scanf("%d", &pi);
                ++p[pi & 1];
            }
            int m;
            scanf("%d", &m);
            while(m--) {
                int qi;
                scanf("%d", &qi);
                ++q[qi & 1];
            }
            printf("%lld
    ", 1ll * p[0]*q[0] + 1ll * p[1]*q[1]);
        }
    }
    
    

    B - Grow The Tree

    题意:有n根木棍,要从原点开始横竖横竖间隔摆,求离原点最远距离。

    题解:很显然不要求间隔摆就直接一根直接插出去,所以可以试试贪心,用最短的一半下整做竖,其他做横。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    int a[100005];
    
    int main() {
    #ifdef KisekiPurin
        freopen("KisekiPurin.in", "r", stdin);
    #endif // KisekiPurin
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        sort(a + 1, a + 1 + n);
        ll sum1 = 0;
        for(int i = 1, c = n / 2; i <= c; ++i)
            sum1 += a[i];
        ll sum2 = 0;
        for(int i = n / 2 + 1; i <= n; ++i)
            sum2 += a[i];
        printf("%lld
    ", sum1 * sum1 + sum2 * sum2);
    }
    

    C - Ivan the Fool and the Probability Theory

    题意:给1e5*1e5级别的棋盘格,称棋盘格“随机”当且仅当它每个格子至多有一个相邻的格子与他同色。

    不会写,数据量略大。

    补题题解:首先考虑只有1行的情况,非常简单,随便dp一下。

    然后有一个引理:当确定第1行之后,后面的行只能和相邻行完全相同或者完全相反。证明:若结论不成立,至少存在一个位置 (j) ,使得 (a_{2,j})(a_{1,j}) 相同,而 (a_{2,j+1})(a_{1,j+1}) 相反,那么:当 (a_{1,j})(a_{1,j+1}) 同色,则 (a_{1,j}) 有两个相邻同色;当 (a_{1,j})(a_{1,j+1}) 相反,则 (a_{2,j}) 有两个相邻同色。且显然的,假如第1行出现了至少1个连续的2格组,那么以后都不能选完全相同,只能选完全相反,对于这种情况只需要确定第一行就可以全部统计完毕。另一种情况是,只有完全间隔的格子可以选择复制一行,且下一次必须选择相反复制,那这种情况压缩到一列去观察,发现和这个问题的1*m版本是一样的(但是不能够乘2,以为原本求出的dp数组就已经是包含两种颜色的)。

    所以就是 ((dp[m]-2)+dp[n])

    启发:不会做的话是不是可以dp出一行或者2行的结果看看规律,然后手画几个小样例看看能不能套上去吧。不过下一次应该是学会这样分析了。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const int MOD = 1e9 + 7;
    int dp[100005][2];
    
    int main() {
    #ifdef KisekiPurin
        freopen("KisekiPurin.in", "r", stdin);
    #endif // KisekiPurin
        int n, m;
        while(~scanf("%d%d", &n, &m)) {
            if(n > m)
                swap(n, m);
            dp[1][0] = 2;
            for(int i = 2; i <= m; ++i) {
                dp[i][0] = (dp[i - 1][1] + dp[i - 1][0]) % MOD;
                dp[i][1] = dp[i - 1][0];
            }
            printf("%lld
    ", ((1ll * dp[m][0] + dp[m][1] - 2ll) % MOD + (1ll * dp[n][0] + dp[n][1]) % MOD) % MOD);
        }
    }
    

    D1 - The World Is Just a Programming Task (Easy Version)

    见下题

    D2 - The World Is Just a Programming Task (Hard Version)

    题意:给一个括号串,可以调换其中的任意两个括号的位置(可以自己换自己),使得新括号串的循环移位构成的合法串最多。多方案任输出其中之一。

    题解:画一下发现,合法括号串,必须是从0开始的不越过0分界线的到0结束的一段折线。那么循环移位的数量就相当于触碰0点的数量。因为是循环移位所以不妨找出最早的一个最低点作为0点。假如有两个以上0点,那么改变其中之一不会变得更好,此时把0点前的一个非0点的顺序换一下有可能会导致答案+1。

    所以口胡出一个做法:先检查是否左右括号平衡,不平衡直接 0 1 1 ,然后检测是否已经满了(都是两个括号间隔着),是则输出 n/2 1 1 ,否则找到零点的数量,若只有一个,则尝试把零点破坏。然后都尝试一下在连续下坡两次的零点前构造一个新的,比比谁多。

    但是很难写。我决定看别人怎么做。

    补题题解:发现别人和我的分析是一样的,不过别人直接指出了,把一个左括号和一个右括号互换,会使得其中夹着的所有的点的y均-2。所以问题转化成了,对于某个高度为1的左括号,一直向右扩展到第一个高度为1的右括号,可以尝试把中间遇到的所有y=2的点翻折下来,而在这个右括号之后假如跟着左括号(回到2)则可以继续,否则break掉。可以发现这就是被零点打断的一些区间,最好的做法就是在交换两个零点之间的括号。复杂度O(n)。

    具体来说,假如编号为0,1,2,3,...,n号点,其中n号和0号是同一个点且他们都是零点,设相邻的某两个零点x1,x2,若x2-x1>2,则尝试交换x1+2,和x2-1。贡献为[x1,x2]中y=2的点的数量。问题转化成怎么找循环位移的零点。这个就不多说了。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    int n;
    char *s, t[600005];
    
    int gans, gai, gaj;
    
    void solve1() {
        //把2翻转成0
        int ans1 = 0, ans2 = 0, ai = 1, aj = 1;
        int cnt0 = 0, cnt2 = 0, lastp = -1;
        for(int i = 1; i <= n; ++i) {
            if(s[i] == '(') {
                ++cnt0;
                if(cnt0 == 1)
                    lastp = i;
                else if(cnt0 == 2)
                    ++cnt2;
            } else {
                --cnt0;
                if(cnt0 == 0)
                    ++ans1;
                else if(cnt0 == 1) {
                    if(cnt2 > ans2) {
                        ans2 = cnt2;
                        ai = lastp + 1;
                        aj = i;
                    }
                    lastp = i;
                    cnt2 = 0;
                } else if(cnt0 == 2)
                    ++cnt2;
            }
        }
        gans = ans1 + ans2;
        gai = ai, gaj = aj;
    }
    
    void solve2() {
        //把1翻转为-1
        int ans = 0, ai = 1, aj = 1;
        int cnt0 = 0, cnt1 = 0, lastp = 0;
        for(int i = 1; i <= n; ++i) {
            if(s[i] == '(') {
                ++cnt0;
                if(cnt0 == 1)
                    ++cnt1;
            } else {
                --cnt0;
                if(cnt0 == 0) {
                    if(cnt1 > ans) {
                        ans = cnt1;
                        ai = lastp + 1;
                        aj = i;
                    }
                    lastp = i;
                    cnt1 = 0;
                } else if(cnt0 == 1)
                    ++cnt1;
            }
        }
        if(ans > gans) {
            gans = ans;
            gai = ai;
            gaj = aj;
        }
        int p = s - t;
        gai += p;
        gaj += p;
        if(gai > n)
            gai -= n;
        if(gaj > n)
            gaj -= n;
        printf("%d
    %d %d
    ", gans, gai, gaj);
    }
    
    int main() {
    #ifdef KisekiPurin
        freopen("KisekiPurin.in", "r", stdin);
    #endif // KisekiPurin
        while(~scanf("%d%s", &n, t + 1)) {
            if(n & 1) {
                printf("0
    1 1
    ");
                continue;
            }
            int cnt = 0, mincnt = 1e9;
            for(int i = 1; i <= n; ++i) {
                t[i + n] = t[i];
                if(t[i] == '(')
                    ++cnt;
                else {
                    --cnt;
                    if(cnt < mincnt) {
                        mincnt = cnt;
                        s = &t[i];
                    }
                }
            }
            if(cnt) {
                printf("0
    1 1
    ");
                continue;
            }
            s[n + 1] = '';
            solve1();
            solve2();
        }
        return 0;
    }
    

    E - Queue in the Train

    题意:有n个人坐火车,第i个人会在ti时刻开始想去泡面,每个人花的时间都是p。泡泡面是要排队的,但是大家都不喜欢排队,每个人去排队时,队列中不可以有编号比他小的人(意思是可以有编号比他大的),若多个人同时想去,则神奇的只有编号最小的会去排队。

    错误尝试:一开始直接按时间排序然后丢进堆里面按编号弹出来,这个错误理解题意,不过吸收了一下这个对时间扫描的写法(以前貌似不会写,现在码力确实上升了)。应该是直接模拟,对时间排序,然后同一个时刻的按编号顺序排。这样每个人丢进堆里面。这样堆按编号小优先的话,同时想去的这堆人就只能是堆顶了。因为堆顶假如不能去则整个堆都不能去,这是显然的。

    所以就再整一个队列。每次查一个位置前面的人是不是有人在队列中的话,用树状数组就可以(求前缀和,貌似从一个二分找奇数偶数前缀和的题目得到启发)。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    int n, p;
    pair<int, int> t[100005];
    priority_queue<int, vector<int>, greater<int> >pq;
    queue<int> q;
    ll ans[100005];
    
    int bit[100005];
    
    void add(int x, int v) {
        for(; x <= n; x += x & -x)
            bit[x] += v;
    }
    
    int sum(int x) {
        int res = 0;
        for(; x; x -= x & -x)
            res += bit[x];
        return res;
    }
    
    int main() {
    #ifdef KisekiPurin
        freopen("KisekiPurin.in", "r", stdin);
    #endif // KisekiPurin
        scanf("%d%d", &n, &p);
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &t[i].first);
            t[i].second = i;
        }
        sort(t + 1, t + 1 + n);
        ll curp = 0;
        int i = 1, cnt = 0;
        while(cnt < n) {
            if(q.empty() && pq.empty() && i <= n && curp < t[i].first)
                curp = t[i].first;
            if(q.size()) {
                int u = q.front();
                curp += p;
                ans[u] = curp;
                add(u, -1);
                q.pop();
                ++cnt;
            }
            while(i <= n && t[i].first <= curp) {
                pq.push(t[i].second);
                ++i;
            }
            while(pq.size()) {
                int u = pq.top();
                if(sum(u) == 0) {
                    q.push(u);
                    add(u, 1);
                    pq.pop();
                } else
                    break;
            }
        }
        for(int i = 1; i <= n; ++i)
            printf("%lld%c", ans[i], " 
    "[i == n]);
    
        return 0;
    }
    

    使用这个结构貌似容易颠倒一些事件的顺序,正确的做法是把所有的事件写成结构体然后排序(类似cdq分治),有几种事件:1、有人开始想要泡面,2、有人进入了队伍里排队泡面,3、有人泡完面结束了。

    然后发现事件2是可以立刻处理的,就不需要进入优先队列里面了。学习了别人对时间轴的处理方法。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    int n, p;
    struct Event {
        ll t, op, id;
        //时间、事件类型、序号
        Event(ll _t, int _op, int _id) {
            t = _t, op = _op, id = _id;
        }
        bool operator<(const Event &e)const {
            return t != e.t ? t > e.t : (op != e.op ? op > e.op : id > e.id);
        }
    };
    priority_queue<Event> _time;
    set<int> inq, wnt;
    
    ll ans[100005];
    
    int main() {
    #ifdef KisekiPurin
        freopen("KisekiPurin.in", "r", stdin);
    #endif // KisekiPurin
        scanf("%d%d", &n, &p);
        for(int i = 1, ti; i <= n; ++i) {
            scanf("%d", &ti);
            _time.push(Event(ti, 1, i));
        }
        ll curtime = 0;
        while(!_time.empty()) {
            Event e = _time.top();
            _time.pop();
            if(e.op == 1) {
                if(inq.empty() || *inq.begin() > e.id) {
                    curtime = max(curtime, e.t);
                    curtime += p;
                    _time.push(Event(curtime, 3, e.id));
                    inq.insert(e.id);
                } else
                    wnt.insert(e.id);
            } else {
                ans[e.id] = e.t;
                inq.erase(e.id);
                if(!wnt.empty() && (inq.empty() || *inq.begin() > *wnt.begin())) {
                    auto tmp = wnt.begin();
                    curtime += p;
                    _time.push(Event(curtime, 3, *tmp));
                    inq.insert(*tmp);
                    wnt.erase(tmp);
                }
            }
        }
        for(int i = 1; i <= n; ++i)
            printf("%lld%c", ans[i], " 
    "[i == n]);
    
        return 0;
    }
    

    总结1:对这种时间轴,分析得到其实在curtime入队inq之后的下一个有用的1事件必定推迟到curtime+p时发生。而3事件是直接交给时间轴处理的。大概的意思是用优先队列维护一整个时间轴,每次从时间轴的顶部取出一些事件,由于这个题目的约定我们规定事件3早于事件1发生。还是得慢慢领悟。

    总结2:时间轴的事件是不随curtime变化的,这个是priority_queue实现的一个优点,curtime的作用只是给事件的发生时间打好标记(就是入队inq了之后,它完成的时间就是curtime+=p。取出一个事件的时候,若curtime超过t,那么t会推迟到curtime执行,否则是curtime浪费时间空等到t,所以取max(然后再+=p)。也就是curtime记录的只是当前队列的队尾元素完成的时间,而不是当前时间,假如换个名字叫做queue_time会更合适。),而不管时间轴是怎么运作的。总之就是码力不足,知道自己的写法为什么错,但是不知道什么是正确的写法。

    F - Catowice City

    题意:有n个人,选其中j个做裁判,剩下n-j个做参赛选手,要求任意一种合法的分配。合法的分配是指:每个裁判都不认识每个参赛选手。(注意认识关系是单向的,自己必定认识自己)

    题解:看起来就像是一个“传递闭包”,认识关系连一条有向边,然后选择一个点必须选择它的整个闭包作为裁判,假如还有点没有选中很明显就可以全部作为参赛选手。问题变成找一个点,不能出发到达所有的点。

    补题题解:图论是不太会的,直接看别人的参考。有一种方法是求出点强连通分量,那么一个点强连通分量一定是循环选择的,然后强连通的这个分量缩成一个点。假如缩点之后图中只有一个点,那么就No,否则缩点之后必定无环,选择其中一个出度为0的点作为裁判,剩下的作为选手,因为选手可以认识裁判,裁判不能认识选手。

    封装了一个Kosaraju的模板,通过类似网络流的使用方法来添加,实现方式为vector(比前向星略微方便),输出的n2,G2就是缩点后新图的节点数和图的样子,V就是新图每个节点对应的强连通分量,而查询某个点所属的强连通分量则用c2。为了不过多使用::符,在Kosaraju中写一个solve处理新图就可以了。或者using namespace SCC也可以。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    namespace SCC {
        const int MAXN = 1e6 + 5;
    
        int n;
        vector<int> G[MAXN], BG[MAXN];
    
        int c1[MAXN], cntc1;
        int c2[MAXN], cntc2;
        int s[MAXN], cnts;
    
        int n2;
        vector<int> V[MAXN];
        vector<int> G2[MAXN], BG2[MAXN];
    
        void init(int _n) {
            n = _n;
            cntc1 = 0, cntc2 = 0, cnts = 0;
            for(int i = 1; i <= n; ++i) {
                G[i].clear();
                BG[i].clear();
                c1[i] = 0;
                c2[i] = 0;
                s[i] = 0;
                V[i].clear();
                G2[i].clear();
                BG2[i].clear();
            }
        }
    
        void add_edge(int u, int v) {
            G[u].push_back(v);
            BG[v].push_back(u);
        }
    
        void dfs1(int u) {
            c1[u] = cntc1;
            for(int v : G[u]) {
                if(!c1[v])
                    dfs1(v);
            }
            s[++cnts] = u;
        }
    
        void dfs2(int u) {
            V[cntc2].push_back(u);
            c2[u] = cntc2;
            for(int v : BG[u]) {
                if(!c2[v])
                    dfs2(v);
            }
        }
    
        void Kosaraju() {
            for(int i = 1; i <= n; ++i) {
                if(!c1[i]) {
                    ++cntc1;
                    dfs1(i);
                }
            }
    
            for(int i = n; i >= 1; --i) {
                if(!c2[s[i]]) {
                    ++cntc2;
                    dfs2(s[i]);
                }
            }
    
            n2 = cntc2;
            for(int i = 1; i <= n2; ++i) {
                for(auto u : V[i]) {
                    for(auto v : G[u]) {
                        if(c2[v] != i) {
                            G2[i].push_back(c2[v]);
                            BG2[c2[v]].push_back(i);
                        }
                    }
                }
            }
    
            for(int i = 1; i <= n2; ++i) {
                sort(G2[i].begin(), G2[i].end());
                G2[i].erase(unique(G2[i].begin(), G2[i].end()), G2[i].end());
                sort(BG2[i].begin(), BG2[i].end());
                BG2[i].erase(unique(BG2[i].begin(), BG2[i].end()), BG2[i].end());
            }
        }
    
        void solve() {
            int id = -1;
            for(int i = 1; i <= n2; ++i) {
                if(G2[i].size() == 0) {
                    id = i;
                    break;
                }
            }
            printf("%d %d
    ", V[id].size(), n - V[id].size());
            for(int i = 0, siz = V[id].size(); i < siz; ++i)
                printf("%d%c", V[id][i], " 
    "[i == siz - 1]);
            int fi = 1;
            for(int i = 1; i <= n2; ++i) {
                if(i != id) {
                    for(int j = 0, siz = V[i].size(); j < siz; ++j) {
                        if(fi) {
                            printf("%d", V[i][j]);
                            fi = 0;
                        } else
                            printf(" %d", V[i][j]);
                    }
                }
            }
            printf("
    ");
        }
    }
    
    int main() {
    #ifdef KisekiPurin
        freopen("KisekiPurin.in", "r", stdin);
    #endif // KisekiPurin
        int t;
        scanf("%d", &t);
        while(t--) {
            int n, m;
            scanf("%d%d", &n, &m);
    
            SCC::init(n);
            for(int i = 1; i <= m; ++i) {
                int u, v;
                scanf("%d%d", &u, &v);
                SCC::add_edge(u, v);
            }
    
            SCC::Kosaraju();
    
            if(SCC::n2 == 1) {
                printf("No
    ");
                continue;
            }
    
            printf("Yes
    ");
            SCC::solve();
        }
        return 0;
    }
    

    上述算法要跑1000ms,仔细想想是对DAG的多重边排序去重导致的,分析一下发现其实假如是DAG上面dp的话多重边其实无所谓的,正向图甚至也不用存。而这道题更绝,只需要找一个出度为0的缩后新点就可以了,连图都不用存。

    注:貌似强连通缩点后只有一个顶点的次次都是特殊情况。Codeforces貌似可以有行末空格。

    标签:强连通分量

    参考资料:

    Codeforces 1239B. The World Is Just a Programming Task (Hard Version) - LLTYYC - 博客园
    Codeforces 1239D. Catowice City - LLTYYC - 博客园

  • 相关阅读:
    GTP (GPRS隧道协议(GPRSTunnellingProtocol))
    dns (域名系统)
    WSP (无线会话协议)
    http 超文本传输协议
    SSN 社会安全号码
    MD5 Message Digest Algorithm MD5(中文名为消息摘要算法第五版)
    dns
    C/C++中的预编译指令
    strstr 函数用法
    转Hibernate Annotation mappedBy注解理解
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/11817004.html
Copyright © 2020-2023  润新知