大二上第八周学习笔记

周一

A. Luntik and Concerts(思维)

比赛的时候写的比较复杂,分类讨论

注意a, b, c >= 1

另S = a + b * 2 + c * 3

那么可以组成0~S的任何数

可以用贪心放,先放3,再放2,再放1,这样一定可以放完

可以讨论每次3有没有放完,2有没有放完

所以答案就看S的奇偶就行了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        ll a, b, c;
        scanf("%lld%lld%lld", &a, &b, &c);
        ll sum = a + b * 2 + c * 3;
        printf("%d\n", sum % 2);
    }
   
    return 0;
}

D. Vupsen, Pupsen and 0(构造)

很快想到了构造方法,处理奇数的部分出了点问题

我用的式子里有两个数相减,没有考虑可能为0,交上去WA了

于是我就干脆找两个不相同的数相减,特殊情况所有数都相同很好处理

但是我这样写绝对值会变大,就不知道该怎么处理

后来不管了先交,结果AC了

之后看题解发现,如果是奇数情况,最大值一定是1e5-1,也就是最大值-1

所以绝对值多一个数是没关系的。比赛时没想到

还有一点是,构造的时候可以选择两个数相减,也可以选择两个数相加

选两个数相加不为0会好点,因为三个数里面,都不为0,一定存在两个数相加不为0

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int a[N], b[N], n;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &a[i]);

        if(n % 2 == 0)
        {
            _for(i, 1, n)
            {
                if(i <= n / 2) printf("%d ", -a[n - i + 1]);
                else printf("%d ", a[n - i + 1]);
            }
            puts("");
        }
        else
        {
            int p1 = 1, p2 = -1, p3;
            _for(i, 2, n)
                if(a[p1] != a[i])
                {
                    p2 = i;
                    break;
                }
            if(p2 == -1)
            {
                printf("2 ");
                _for(i, 2, n / 2) printf("1 ");
                _for(i, n / 2 + 1, n) printf("-1 ");
                puts("");
                continue;
            }

            _for(i, 1, n)
                if(i != p1 && i != p2)
                {
                    p3 = i;
                    break;
                }
            
            _for(i, 1, n) b[i] = 0;
            int A1 = a[p1], A2 = a[p2], A3 = a[p3];
            b[p1] = -A3;
            b[p2] = A3;
            b[p3] = A1 - A2;

            vector<int> ve;
            _for(i, 1, n)
                if(!b[i])
                    ve.push_back(i);
            for(int i = 0; i < ve.size(); i += 2)
            {
                int l = ve[i], r = ve[i + 1];
                b[l] = a[r];
                b[r] = -a[l];
            }

            _for(i, 1, n) printf("%d ", b[i]); puts("");
        }
    }
   
    return 0;
}

F1. Korney Korneevich and XOR (easy version)(dp)

这是一道dp

看数据范围,可以n * v的 也就是两个循环

这里的关键是怎么保证a的值递增

也就是i < j 且ai < aj

i < j用循环1到n来保证

ai < aj的话,枚举所有异或值,记录每一个异或值对应的最小的a[i]

如果ai < aj就可以转移

最后注意a最大值为500,但异或可能变大,到最接近它的2 ^ n - 1 也就是511

枚举值域的时候注意

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int dp[N], a[N], n;

int main()
{ 
    scanf("%d", &n);
    _for(i, 1, n) scanf("%d", &a[i]);

    memset(dp, 0x3f, sizeof dp);
    dp[0] = -1;
    _for(i, 1, n)
        _for(j, 0, 512)
            if(dp[j] < a[i])
                dp[j ^ a[i]] = min(dp[j ^ a[i]], a[i]);
        
    vector<int> ans;
    _for(i, 0, 512) 
        if(dp[i] < 1e9)
            ans.push_back(i);
    printf("%d\n", ans.size());
    for(int x: ans) printf("%d ", x); puts("");        
   
    return 0;
}

周二

Tree Constructer(构造 + 二分图)

完全没往二分图这个方向想

树也是二分图,因为可以分层染色,使得节点分成两份,同一份之间没有边相连

直接分层黑白染色分成两份

首先保证同一份之间没有连边,可以在左边的点最高位全部为0,右边的点最高位为1次高位为0

然后给左边分配id,id位为0其他都为1,然后与其相连的右边点都id为1,其他为0

这样构造就刚好符合题目条件

这里注意需要id+2位,n等于100 只有60位

所以选点数小的做为左边的点

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 100 + 10;
int g[N][N], ans[N][N], n, cnt;
vector<int> v1, v2;

void dfs(int u, int fa, int d)
{
    if(d % 2) v1.push_back(u);
    else v2.push_back(u);

    _for(v, 0, 100)
        if(g[u][v])
        {
            if(v == fa) continue;
             dfs(v, u, d + 1);
        }
}

int main()
{ 
    scanf("%d", &n);
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u][v] = g[v][u] = 1;
    }

    dfs(1, 0, 0);
    if(v1.size() > v2.size()) swap(v1, v2);
    for(int u: v1)
    {
        _for(i, 1, 59) ans[u][i] = 1;
        ++cnt;
        ans[u][cnt] = 0;
        _for(v, 1, n)
            if(g[u][v])
                ans[v][cnt] = 1;
    }
    for(int v: v2) ans[v][60] = 1;

    _for(i, 1, n)
    {
        ll x = 0;
        for(int j = 60; j >= 1; j--)
            x = x * 2 + ans[i][j];
        printf("%lld ", x);
    }
   
    return 0;
}

E. Pchelyonok and Segments(dp)

其实不是特别难的dp

dp[i][j]表示后i个,最大长度为j的和的最大值

可以分i是不是成不成为一段来写转移方程

注意边界条件和细节

首先j=0时要赋成最大值,表示一定可以转移

j!=0时赋为最小值,表示不合法

注意是i = n + 1的时候

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 2e5 + 10;
const int M = 450;
int dp[N][M], n;
ll a[N], s[N];

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) 
        {
            scanf("%lld", &a[i]);
            s[i] = s[i - 1] + a[i];
        }

        int k = 1;
        while((k + 1) * (k + 2) / 2 <= n) k++;
        _for(j, 1, k) dp[n + 1][j] = 0;
        dp[n + 1][0] = 2e9;

        for(int i = n; i >= 1; i--)
            _for(j, 0, k)
            {
                dp[i][j] = dp[i + 1][j];
                if(j && i + j - 1 <= n && s[i + j - 1] - s[i - 1] < dp[i + j][j - 1])
                    dp[i][j] = max((ll)dp[i][j], s[i + j - 1] - s[i - 1]);
            }
        
        for(int i = k; i >= 1; i--)
            if(dp[1][i] > 0)
            {
                printf("%d\n", i);
                break;
            }
    }
  
    return 0;
}

D. Red-Green Towers(dp统计方案数)

同一题在不同的题单遇到

背包统计方案数

然后根据上下界统计答案就好了

注意下界要大于等于0,因为可能算出负值

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
const int mod = 1e9 + 7;
int dp[N], r, g, h;

int main()
{ 
    scanf("%d%d", &r, &g);
    while((h + 1) * (h + 2) / 2 <= r + g) h++;

    dp[0] = 1;
    _for(i, 1, h)
        for(int j = r; j >= i; j--)
            dp[j] = (dp[j] + dp[j - i]) % mod;

    int ans = 0;
    _for(i, max(h * (h + 1) / 2 - g, 0), r)
        ans = (ans + dp[i]) % mod;
    printf("%d\n", ans);

    return 0;
}

E. Number of Simple Paths(思维)

如果是树的话,两两之间只有一条路径,很容易统计

然后加了一条边,我想统计因为这条边而增加的路径,也就是经过这条边的路径,发现非常复杂,很难统计,就不知道了

看了题解,一颗树加一条边就是基环树

可以看成一个环,然后环上的一些节点挂着一颗树

这个时候发现很多两点之间都有两条路径

发现只有在一颗树上才是两两之间一条路径,所以统计树的节点个数减去就好了

实现上,无向图找环就用类似拓扑排序,度数为1就加入队列,最后没加入队列的点就是环上的点

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 2e5 + 10;
vector<int> g[N];
int degree[N], vis[N], n, cnt;

void dfs(int u, int fa)
{
    cnt++;
    for(int v: g[u])
    {
        if(v == fa || !vis[v]) continue;
        dfs(v, u);
    }
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) g[i].clear(), vis[i] = 0, degree[i] = 0;
        _for(i, 1, n)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
            degree[u]++; degree[v]++;
        }

        queue<int> q;
        _for(i, 1, n)
            if(degree[i] == 1)
                q.push(i);
        while(!q.empty())
        {
            int u = q.front(); q.pop();
            vis[u] = 1;
            for(int v: g[u])
                if(--degree[v] == 1)
                    q.push(v);
        }

        ll res = 1LL * n * (n - 1);
        _for(i, 1, n)
            if(!vis[i])
            {
                cnt = 0;
                dfs(i, 0);
                res -= 1LL * cnt * (cnt - 1) / 2;
            }
        printf("%lld\n", res);
    }

    return 0;
}

周三

E. Compress Words(kmp)

字符串匹配用kmp

每次用kmp匹配最长的后缀等于前缀就行了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e6 + 10;
int Next[N], lens, lena, n;
string s, ans;

void get_next()
{
    Next[0] = -1;
    int i = 0, j = -1;
    lens = s.size();
    while(i < lens)
    {
        if(j == -1 || s[i] == s[j]) 
        {
            i++; j++;
            Next[i] = j;
        }
        else j = Next[j];
    }
}

int kmp()
{
    int j = 0, i = max(lena - lens, 0);
    while(i < lena)
    {
        if(j == -1 || ans[i] == s[j]) i++, j++;
        else j = Next[j];
    }
    return j;
}

int main()
{ 
    scanf("%d", &n);
    cin >> ans;
    lena = ans.size();
    _for(i, 2, n)
    {
        cin >> s;
        get_next();
        int j = kmp();
        ans += string(s, j);
        lena += lens - j;
    }
    cout << ans << endl;
    
    return 0;
}

D. Book of Evil(树的直径)

对树的直径不熟导致没做出来

有一个很重要的性质,离一个点距离最远的点一定是树的直径的端点之一

因此求树的直径可以随便找一个点,找最远点,那就是端点之一,然后再以那个端点为起点就可以找到另一个端点

这道题,其实就是求特殊点的直径

每一个点离它最远的特殊点一定是直径之一

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int d1[N], d2[N], p[N], k[N], n, m, maxd, s1, s2, t;
vector<int> g[N];

void dfs(int u, int fa, int& s, int* d)
{
    d[u] = d[fa] + 1;
    if(p[u] && d[u] > d[s]) s = u;
    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs(v, u, s, d);
    }
}

int main()
{ 
    scanf("%d%d%d", &n, &m, &maxd);
    _for(i, 1, m)
    {
        int x; scanf("%d", &x);
        p[x] = 1;
    }
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs(1, 0, s1, k); 
    dfs(s1, 0, s2, d1);
    dfs(s2, 0, t, d2);

    int ans = 0;
    _for(i, 1, n)
        if(d1[i] - 1 <= maxd && d2[i] - 1 <= maxd)
            ans++;
    printf("%d\n", ans);

    return 0;
}

周日

Simone and Graph Coloring(贪心 + 线段树)

首先是贪心,每次选能选的最小的

然后可以发现,与其相连的一定是比它大的,因此这些的颜色一定是连续的

所以用线段树维护区间最大值最小值就好了

写的时候有细节需要注意

1.查询操作的时候有时候会出现 R > L的情况,这种情况要考虑怎么处理

2.统计答案的时候,若第一个先赋值了,那么ans就要赋第一个对应的答案。

#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e6 + 10;
int t[N << 4][2];
int a[N], c[N], ans, n;

void up(int k)
{
    t[k][0] = max(t[l(k)][0], t[r(k)][0]);
    t[k][1] = min(t[l(k)][1], t[r(k)][1]);
}

void build(int k, int l, int r)
{
    if(l == r)
    {
        t[k][1] = 1e9;
        t[k][0] = 0;
        return;
    }
    int m = l + r >> 1;
    build(l(k), l, m);
    build(r(k), m + 1, r);
    up(k);
}

void add(int k, int l, int r, int x, int p)
{
    if(l == r)
    {
        t[k][0] = t[k][1] = p;
        return;
    }
    int m = l + r >> 1;
    if(x <= m) add(l(k), l, m, x, p);
    else add(r(k), m + 1, r, x, p);
    up(k);
}

int query0(int k, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return t[k][0];
    int m = l + r >> 1, res = 0;
    if(L <= m) res = max(res, query0(l(k), l, m, L, R));
    if(R > m) res = max(res, query0(r(k), m + 1, r, L, R));
    return res;
}

int query1(int k, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return t[k][1];
    int m = l + r >> 1, res = 1e9;
    if(L <= m) res = min(res, query1(l(k), l, m, L, R));
    if(R > m) res = min(res, query1(r(k), m + 1, r, L, R));
    return res;
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &a[i]);
        build(1, 1, n);

        c[1] = 1; ans = 1; //ans = 1关键!!
        add(1, 1, n, a[1], c[1]);
        _for(i, 2, n)
        {
            if(a[i] == n) c[i] = 1; // !!
            else
            {
                int mx = query0(1, 1, n, a[i] + 1, n);
                int mi = query1(1, 1, n, a[i] + 1, n);
                if(mi > 1) c[i] = 1;
                else c[i] = mx + 1;
            }
            add(1, 1, n, a[i], c[i]);
            ans = max(ans, c[i]);
        }

        printf("%d\n", ans);
        _for(i, 1, n) printf("%d ", c[i]); puts("");    
    }

    return 0;
}

Parallel Sort(思维)

首先,我们可以让一个数到它应该去的位置连一个有向边

这样就可以形成很多个不相交的环

一个位置有一条入边,一条出边,如果环相交就不满足这个条件

那么其实就是要看一个环怎么处理

这个挺巧妙的,是队友想到了

先一次对称变换,然后就可以两两交换了

画个图就知道了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int g[N], vis[N], cnt, n;
vector<int> c[N];

void dfs(int u, int id) 
{
    if(vis[u]) return;
    vis[u] = 1;
    c[id].push_back(u);
    dfs(g[u], id);
}

int main()
{ 
    scanf("%d", &n);
    _for(i, 1, n) 
    {
        int x; scanf("%d", &x);
        g[x] = i;
    }

    int ok = 0;
    _for(i, 1, n)
    {
        if(g[i] == i) continue;
        if(!vis[i]) dfs(i, ++cnt);
        ok = 1;
    }
    if(!ok)
    {
        puts("0");
        return 0;
    }

    vector<int> pr[2];
    _for(i, 1, cnt)
    {
        vector<int> t = c[i];
        int l = 0, r = t.size() - 1;
        while(l < r)
        {
            pr[0].push_back(t[l]); 
            pr[0].push_back(t[r]); 
            l++; r--;
        }
        
        l = 0, r = t.size() - 2;
        while(l < r)
        {
            pr[1].push_back(t[l]); 
            pr[1].push_back(t[r]); 
            l++; r--;
        }
    }

    if(pr[1].size()) printf("%d\n", 2);
    else printf("%d\n", 1);

    printf("%d", pr[0].size() / 2);
    for(int x: pr[0]) printf(" %d", x); puts("");
    if(pr[1].size())
    {
        printf("%d", pr[1].size() / 2);
        for(int x: pr[1]) printf(" %d", x); puts("");
    }

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值